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