1
18 package io.undertow.security.impl;
19
20 import io.undertow.UndertowLogger;
21 import io.undertow.UndertowMessages;
22 import io.undertow.security.api.AuthenticationMechanism;
23 import io.undertow.security.api.AuthenticationMechanism.AuthenticationMechanismOutcome;
24 import io.undertow.security.api.AuthenticationMechanism.ChallengeResult;
25 import io.undertow.security.api.AuthenticationMechanismContext;
26 import io.undertow.security.api.AuthenticationMode;
27 import io.undertow.security.idm.Account;
28 import io.undertow.security.idm.IdentityManager;
29 import io.undertow.security.idm.PasswordCredential;
30 import io.undertow.server.HttpServerExchange;
31 import io.undertow.util.StatusCodes;
32
33 import java.security.AccessController;
34 import java.security.PrivilegedAction;
35 import java.util.Collections;
36 import java.util.LinkedList;
37 import java.util.List;
38
39
45 public class SecurityContextImpl extends AbstractSecurityContext implements AuthenticationMechanismContext {
46
47
48 private static final RuntimePermission PERMISSION = new RuntimePermission("MODIFY_UNDERTOW_SECURITY_CONTEXT");
49
50 private AuthenticationState authenticationState = AuthenticationState.NOT_ATTEMPTED;
51 private final AuthenticationMode authenticationMode;
52
53 private String programaticMechName = "Programatic";
54
55
59 private Node<AuthenticationMechanism> authMechanisms = null;
60 private final IdentityManager identityManager;
61
62 public SecurityContextImpl(final HttpServerExchange exchange, final IdentityManager identityManager) {
63 this(exchange, AuthenticationMode.PRO_ACTIVE, identityManager);
64 }
65
66 public SecurityContextImpl(final HttpServerExchange exchange, final AuthenticationMode authenticationMode, final IdentityManager identityManager) {
67 super(exchange);
68 this.authenticationMode = authenticationMode;
69 this.identityManager = identityManager;
70 if (System.getSecurityManager() != null) {
71 System.getSecurityManager().checkPermission(PERMISSION);
72 }
73 }
74
75
83
84 @Override
85 public boolean authenticate() {
86 UndertowLogger.SECURITY_LOGGER.debugf("Attempting to authenticate %s, authentication required: %s", exchange.getRequestPath(), isAuthenticationRequired());
87 if(authenticationState == AuthenticationState.ATTEMPTED || (authenticationState == AuthenticationState.CHALLENGE_SENT && !exchange.isResponseStarted())) {
88
89
90 authenticationState = AuthenticationState.NOT_ATTEMPTED;
91 }
92 return !authTransition();
93 }
94
95 private boolean authTransition() {
96 if (authTransitionRequired()) {
97 switch (authenticationState) {
98 case NOT_ATTEMPTED:
99 authenticationState = attemptAuthentication();
100 break;
101 case ATTEMPTED:
102 authenticationState = sendChallenges();
103 break;
104 default:
105 throw new IllegalStateException("It should not be possible to reach this.");
106 }
107 return authTransition();
108
109 } else {
110 UndertowLogger.SECURITY_LOGGER.debugf("Authentication result was %s for %s", authenticationState, exchange.getRequestPath());
111
112 switch (authenticationState) {
113 case NOT_ATTEMPTED:
114 case ATTEMPTED:
115 case AUTHENTICATED:
116 return false;
117 default:
118
119 return true;
120 }
121 }
122 }
123
124 private AuthenticationState attemptAuthentication() {
125 return new AuthAttempter(authMechanisms,exchange).transition();
126 }
127
128 private AuthenticationState sendChallenges() {
129 UndertowLogger.SECURITY_LOGGER.debugf("Sending authentication challenge for %s", exchange);
130 return new ChallengeSender(authMechanisms, exchange).transition();
131 }
132
133 private boolean authTransitionRequired() {
134 switch (authenticationState) {
135 case NOT_ATTEMPTED:
136
137
138 return isAuthenticationRequired() || authenticationMode == AuthenticationMode.PRO_ACTIVE;
139 case ATTEMPTED:
140
141
142 return isAuthenticationRequired();
143 default:
144
145
146 return false;
147 }
148 }
149
150
155 public void setProgramaticMechName(final String programaticMechName) {
156 this.programaticMechName = programaticMechName;
157 }
158
159 @Override
160 public void addAuthenticationMechanism(final AuthenticationMechanism handler) {
161
162 if(authMechanisms == null) {
163 authMechanisms = new Node<>(handler);
164 } else {
165 Node<AuthenticationMechanism> cur = authMechanisms;
166 while (cur.next != null) {
167 cur = cur.next;
168 }
169 cur.next = new Node<>(handler);
170 }
171 }
172
173 @Override
174 @Deprecated
175 public List<AuthenticationMechanism> getAuthenticationMechanisms() {
176 List<AuthenticationMechanism> ret = new LinkedList<>();
177 Node<AuthenticationMechanism> cur = authMechanisms;
178 while (cur != null) {
179 ret.add(cur.item);
180 cur = cur.next;
181 }
182 return Collections.unmodifiableList(ret);
183 }
184
185 @Override
186 @Deprecated
187 public IdentityManager getIdentityManager() {
188 return identityManager;
189 }
190
191 @Override
192 public boolean login(final String username, final String password) {
193
194 UndertowLogger.SECURITY_LOGGER.debugf("Attempting programatic login for user %s for request %s", username, exchange);
195
196 final Account account;
197 if(System.getSecurityManager() == null) {
198 account = identityManager.verify(username, new PasswordCredential(password.toCharArray()));
199 } else {
200 account = AccessController.doPrivileged(new PrivilegedAction<Account>() {
201 @Override
202 public Account run() {
203 return identityManager.verify(username, new PasswordCredential(password.toCharArray()));
204 }
205 });
206 }
207
208 if (account == null) {
209 return false;
210 }
211
212 authenticationComplete(account, programaticMechName, true);
213 this.authenticationState = AuthenticationState.AUTHENTICATED;
214
215 return true;
216 }
217
218 @Override
219 public void logout() {
220 Account authenticatedAccount = getAuthenticatedAccount();
221 if(authenticatedAccount != null) {
222 UndertowLogger.SECURITY_LOGGER.debugf("Logging out user %s for %s", authenticatedAccount.getPrincipal().getName(), exchange);
223 } else {
224 UndertowLogger.SECURITY_LOGGER.debugf("Logout called with no authenticated user in exchange %s", exchange);
225 }
226 super.logout();
227 this.authenticationState = AuthenticationState.NOT_ATTEMPTED;
228 }
229
230
231 private class AuthAttempter {
232
233 private Node<AuthenticationMechanism> currentMethod;
234 private final HttpServerExchange exchange;
235
236 private AuthAttempter(Node<AuthenticationMechanism> currentMethod, final HttpServerExchange exchange) {
237 this.exchange = exchange;
238 this.currentMethod = currentMethod;
239 }
240
241 private AuthenticationState transition() {
242 if (currentMethod != null) {
243 final AuthenticationMechanism mechanism = currentMethod.item;
244 currentMethod = currentMethod.next;
245 AuthenticationMechanismOutcome outcome = mechanism.authenticate(exchange, SecurityContextImpl.this);
246 if(UndertowLogger.SECURITY_LOGGER.isDebugEnabled()) {
247 UndertowLogger.SECURITY_LOGGER.debugf("Authentication outcome was %s with method %s for %s", outcome, mechanism, exchange.getRequestURI());
248 if(UndertowLogger.SECURITY_LOGGER.isTraceEnabled()) {
249 UndertowLogger.SECURITY_LOGGER.tracef("Contents of exchange after authentication attempt is %s", exchange);
250 }
251 }
252
253 if (outcome == null) {
254 throw UndertowMessages.MESSAGES.authMechanismOutcomeNull();
255 }
256
257 switch (outcome) {
258 case AUTHENTICATED:
259
260 return AuthenticationState.AUTHENTICATED;
261 case NOT_AUTHENTICATED:
262
263
264 setAuthenticationRequired();
265 return AuthenticationState.ATTEMPTED;
266 case NOT_ATTEMPTED:
267
268 return transition();
269 default:
270 throw new IllegalStateException();
271 }
272
273 } else {
274
275 return AuthenticationState.ATTEMPTED;
276 }
277 }
278
279 }
280
281
284 private class ChallengeSender {
285
286 private Node<AuthenticationMechanism> currentMethod;
287 private final HttpServerExchange exchange;
288
289 private Integer chosenStatusCode = null;
290 private boolean challengeSent = false;
291
292 private ChallengeSender(Node<AuthenticationMechanism> currentMethod, final HttpServerExchange exchange) {
293 this.exchange = exchange;
294 this.currentMethod = currentMethod;
295 }
296
297 private AuthenticationState transition() {
298 if (currentMethod != null) {
299 final AuthenticationMechanism mechanism = currentMethod.item;
300 currentMethod = currentMethod.next;
301 ChallengeResult result = mechanism.sendChallenge(exchange, SecurityContextImpl.this);
302 if(result == null) {
303 throw UndertowMessages.MESSAGES.sendChallengeReturnedNull(mechanism);
304 }
305 if (result.isChallengeSent()) {
306 challengeSent = true;
307 Integer desiredCode = result.getDesiredResponseCode();
308 if (desiredCode != null && (chosenStatusCode == null || chosenStatusCode.equals(StatusCodes.OK))) {
309 chosenStatusCode = desiredCode;
310 if (chosenStatusCode.equals(StatusCodes.OK) == false) {
311 if(!exchange.isResponseStarted()) {
312 exchange.setStatusCode(chosenStatusCode);
313 }
314 }
315 }
316 }
317
318
319 return transition();
320
321 } else {
322 if(!exchange.isResponseStarted()) {
323
324 if (chosenStatusCode == null) {
325 if (challengeSent == false) {
326
327 exchange.setStatusCode(StatusCodes.FORBIDDEN);
328 }
329 } else if (chosenStatusCode.equals(StatusCodes.OK)) {
330 exchange.setStatusCode(chosenStatusCode);
331 }
332 }
333
334 return AuthenticationState.CHALLENGE_SENT;
335 }
336 }
337
338 }
339
340
343 enum AuthenticationState {
344 NOT_ATTEMPTED,
345
346 ATTEMPTED,
347
348 AUTHENTICATED,
349
350 CHALLENGE_SENT;
351 }
352
353
357 private static final class Node<T> {
358 final T item;
359 Node<T> next;
360
361 private Node(T item) {
362 this.item = item;
363 }
364 }
365
366 }
367