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