1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 package org.apache.hc.client5.http.impl.auth;
28
29 import java.io.Serializable;
30 import java.nio.charset.Charset;
31 import java.nio.charset.StandardCharsets;
32 import java.security.Principal;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Locale;
36 import java.util.Map;
37
38 import org.apache.hc.client5.http.auth.AuthChallenge;
39 import org.apache.hc.client5.http.auth.AuthScheme;
40 import org.apache.hc.client5.http.auth.AuthScope;
41 import org.apache.hc.client5.http.auth.AuthStateCacheable;
42 import org.apache.hc.client5.http.auth.AuthenticationException;
43 import org.apache.hc.client5.http.auth.Credentials;
44 import org.apache.hc.client5.http.auth.CredentialsProvider;
45 import org.apache.hc.client5.http.auth.MalformedChallengeException;
46 import org.apache.hc.client5.http.auth.StandardAuthScheme;
47 import org.apache.hc.client5.http.auth.UsernamePasswordCredentials;
48 import org.apache.hc.client5.http.impl.StateHolder;
49 import org.apache.hc.client5.http.protocol.HttpClientContext;
50 import org.apache.hc.client5.http.utils.Base64;
51 import org.apache.hc.client5.http.utils.ByteArrayBuilder;
52 import org.apache.hc.core5.annotation.Internal;
53 import org.apache.hc.core5.http.HttpHost;
54 import org.apache.hc.core5.http.HttpRequest;
55 import org.apache.hc.core5.http.NameValuePair;
56 import org.apache.hc.core5.http.protocol.HttpContext;
57 import org.apache.hc.core5.util.Args;
58 import org.slf4j.Logger;
59 import org.slf4j.LoggerFactory;
60
61
62
63
64
65
66 @AuthStateCacheable
67 public class BasicScheme implements AuthScheme, StateHolder<BasicScheme.State>, Serializable {
68
69 private static final long serialVersionUID = -1931571557597830536L;
70
71 private static final Logger LOG = LoggerFactory.getLogger(BasicScheme.class);
72
73 private final Map<String, String> paramMap;
74 private transient ByteArrayBuilder buffer;
75 private transient Base64 base64codec;
76 private boolean complete;
77
78 private UsernamePasswordCredentials credentials;
79
80
81
82
83
84
85
86 @Deprecated
87 public BasicScheme(final Charset charset) {
88 this.paramMap = new HashMap<>();
89 this.complete = false;
90 }
91
92
93
94
95
96
97 public BasicScheme() {
98 this.paramMap = new HashMap<>();
99 this.complete = false;
100 }
101
102 public void initPreemptive(final Credentials credentials) {
103 if (credentials != null) {
104 Args.check(credentials instanceof UsernamePasswordCredentials,
105 "Unsupported credential type: " + credentials.getClass());
106 this.credentials = (UsernamePasswordCredentials) credentials;
107 this.complete = true;
108 } else {
109 this.credentials = null;
110 }
111 }
112
113 @Override
114 public String getName() {
115 return StandardAuthScheme.BASIC;
116 }
117
118 @Override
119 public boolean isConnectionBased() {
120 return false;
121 }
122
123 @Override
124 public String getRealm() {
125 return this.paramMap.get("realm");
126 }
127
128 @Override
129 public void processChallenge(
130 final AuthChallenge authChallenge,
131 final HttpContext context) throws MalformedChallengeException {
132 this.paramMap.clear();
133 final List<NameValuePair> params = authChallenge.getParams();
134 if (params != null) {
135 for (final NameValuePair param: params) {
136 this.paramMap.put(param.getName().toLowerCase(Locale.ROOT), param.getValue());
137 }
138 }
139 this.complete = true;
140 }
141
142 @Override
143 public boolean isChallengeComplete() {
144 return this.complete;
145 }
146
147 @Override
148 public boolean isResponseReady(
149 final HttpHost host,
150 final CredentialsProvider credentialsProvider,
151 final HttpContext context) throws AuthenticationException {
152
153 Args.notNull(host, "Auth host");
154 Args.notNull(credentialsProvider, "CredentialsProvider");
155
156 final AuthScope authScope = new AuthScope(host, getRealm(), getName());
157 final Credentials credentials = credentialsProvider.getCredentials(
158 authScope, context);
159 if (credentials instanceof UsernamePasswordCredentials) {
160 this.credentials = (UsernamePasswordCredentials) credentials;
161 return true;
162 }
163
164 if (LOG.isDebugEnabled()) {
165 final HttpClientContext clientContext = HttpClientContext.cast(context);
166 final String exchangeId = clientContext.getExchangeId();
167 LOG.debug("{} No credentials found for auth scope [{}]", exchangeId, authScope);
168 }
169 this.credentials = null;
170 return false;
171 }
172
173 @Override
174 public Principal getPrincipal() {
175 return null;
176 }
177
178 private void validateUsername() throws AuthenticationException {
179 if (credentials == null) {
180 throw new AuthenticationException("User credentials not set");
181 }
182 final String username = credentials.getUserName();
183 for (int i = 0; i < username.length(); i++) {
184 final char ch = username.charAt(i);
185 if (Character.isISOControl(ch)) {
186 throw new AuthenticationException("Username must not contain any control characters");
187 }
188 if (ch == ':') {
189 throw new AuthenticationException("Username contains a colon character and is invalid");
190 }
191 }
192 }
193
194 private void validatePassword() throws AuthenticationException {
195 if (credentials == null) {
196 throw new AuthenticationException("User credentials not set");
197 }
198 final char[] password = credentials.getUserPassword();
199 if (password != null) {
200 for (final char ch : password) {
201 if (Character.isISOControl(ch)) {
202 throw new AuthenticationException("Password must not contain any control characters");
203 }
204 }
205 }
206 }
207
208 @Override
209 public String generateAuthResponse(
210 final HttpHost host,
211 final HttpRequest request,
212 final HttpContext context) throws AuthenticationException {
213 validateUsername();
214 validatePassword();
215 if (this.buffer == null) {
216 this.buffer = new ByteArrayBuilder(64);
217 } else {
218 this.buffer.reset();
219 }
220 final Charset charset = AuthSchemeSupport.parseCharset(paramMap.get("charset"), StandardCharsets.UTF_8);
221 this.buffer.charset(charset);
222 this.buffer.append(this.credentials.getUserName()).append(":").append(this.credentials.getUserPassword());
223 if (this.base64codec == null) {
224 this.base64codec = new Base64();
225 }
226 final byte[] encodedCreds = this.base64codec.encode(this.buffer.toByteArray());
227 this.buffer.reset();
228 return StandardAuthScheme.BASIC + " " + new String(encodedCreds, 0, encodedCreds.length, StandardCharsets.US_ASCII);
229 }
230
231 @Override
232 public State store() {
233 if (complete) {
234 return new State(new HashMap<>(paramMap), credentials);
235 } else {
236 return null;
237 }
238 }
239
240 @Override
241 public void restore(final State state) {
242 if (state != null) {
243 paramMap.clear();
244 paramMap.putAll(state.params);
245 credentials = state.credentials;
246 complete = true;
247 }
248 }
249
250 @Override
251 public String toString() {
252 return getName() + this.paramMap;
253 }
254
255 @Internal
256 public static class State {
257
258 final Map<String, String> params;
259 final UsernamePasswordCredentials credentials;
260
261 State(final Map<String, String> params, final UsernamePasswordCredentials credentials) {
262 this.params = params;
263 this.credentials = credentials;
264 }
265
266 @Override
267 public String toString() {
268 return "State{" +
269 "params=" + params +
270 '}';
271 }
272 }
273
274 }