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 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 public void initPreemptive(final Credentials credentials) {
97 if (credentials != null) {
98 Args.check(credentials instanceof UsernamePasswordCredentials,
99 "Unsupported credential type: " + credentials.getClass());
100 this.credentials = (UsernamePasswordCredentials) credentials;
101 } else {
102 this.credentials = null;
103 }
104 }
105
106 @Override
107 public String getName() {
108 return StandardAuthScheme.BASIC;
109 }
110
111 @Override
112 public boolean isConnectionBased() {
113 return false;
114 }
115
116 @Override
117 public String getRealm() {
118 return this.paramMap.get("realm");
119 }
120
121 @Override
122 public void processChallenge(
123 final AuthChallenge authChallenge,
124 final HttpContext context) throws MalformedChallengeException {
125 this.paramMap.clear();
126 final List<NameValuePair> params = authChallenge.getParams();
127 if (params != null) {
128 for (final NameValuePair param: params) {
129 this.paramMap.put(param.getName().toLowerCase(Locale.ROOT), param.getValue());
130 }
131 }
132 this.complete = true;
133 }
134
135 @Override
136 public boolean isChallengeComplete() {
137 return this.complete;
138 }
139
140 @Override
141 public boolean isResponseReady(
142 final HttpHost host,
143 final CredentialsProvider credentialsProvider,
144 final HttpContext context) throws AuthenticationException {
145
146 Args.notNull(host, "Auth host");
147 Args.notNull(credentialsProvider, "CredentialsProvider");
148
149 final AuthScope authScope = new AuthScope(host, getRealm(), getName());
150 final Credentials credentials = credentialsProvider.getCredentials(
151 authScope, context);
152 if (credentials instanceof UsernamePasswordCredentials) {
153 this.credentials = (UsernamePasswordCredentials) credentials;
154 return true;
155 }
156
157 if (LOG.isDebugEnabled()) {
158 final HttpClientContext clientContext = HttpClientContext.adapt(context);
159 final String exchangeId = clientContext.getExchangeId();
160 LOG.debug("{} No credentials found for auth scope [{}]", exchangeId, authScope);
161 }
162 this.credentials = null;
163 return false;
164 }
165
166 @Override
167 public Principal getPrincipal() {
168 return null;
169 }
170
171 private void validateUsername() throws AuthenticationException {
172 if (credentials == null) {
173 throw new AuthenticationException("User credentials not set");
174 }
175 final String username = credentials.getUserName();
176 for (int i = 0; i < username.length(); i++) {
177 final char ch = username.charAt(i);
178 if (Character.isISOControl(ch)) {
179 throw new AuthenticationException("Username must not contain any control characters");
180 }
181 if (ch == ':') {
182 throw new AuthenticationException("Username contains a colon character and is invalid");
183 }
184 }
185 }
186
187 @Override
188 public String generateAuthResponse(
189 final HttpHost host,
190 final HttpRequest request,
191 final HttpContext context) throws AuthenticationException {
192 validateUsername();
193 if (this.buffer == null) {
194 this.buffer = new ByteArrayBuilder(64);
195 } else {
196 this.buffer.reset();
197 }
198 final Charset charset = AuthSchemeSupport.parseCharset(paramMap.get("charset"), defaultCharset);
199 this.buffer.charset(charset);
200 this.buffer.append(this.credentials.getUserName()).append(":").append(this.credentials.getUserPassword());
201 if (this.base64codec == null) {
202 this.base64codec = new Base64();
203 }
204 final byte[] encodedCreds = this.base64codec.encode(this.buffer.toByteArray());
205 this.buffer.reset();
206 return StandardAuthScheme.BASIC + " " + new String(encodedCreds, 0, encodedCreds.length, StandardCharsets.US_ASCII);
207 }
208
209 private void writeObject(final ObjectOutputStream out) throws IOException {
210 out.defaultWriteObject();
211 out.writeUTF(this.defaultCharset.name());
212 }
213
214 @SuppressWarnings("unchecked")
215 private void readObject(final ObjectInputStream in) throws IOException, ClassNotFoundException {
216 in.defaultReadObject();
217 try {
218 this.defaultCharset = Charset.forName(in.readUTF());
219 } catch (final UnsupportedCharsetException ex) {
220 this.defaultCharset = StandardCharsets.US_ASCII;
221 }
222 }
223
224 private void readObjectNoData() {
225 }
226
227 @Override
228 public String toString() {
229 return getName() + this.paramMap;
230 }
231
232 }