-
Notifications
You must be signed in to change notification settings - Fork 276
/
UserJWTController.java
137 lines (114 loc) · 6.05 KB
/
UserJWTController.java
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package de.tum.in.www1.artemis.web.rest;
import java.util.Optional;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.util.Pair;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticatedPrincipal;
import org.springframework.web.bind.annotation.*;
import de.tum.in.www1.artemis.security.SecurityUtils;
import de.tum.in.www1.artemis.security.UserNotActivatedException;
import de.tum.in.www1.artemis.security.jwt.JWTCookieService;
import de.tum.in.www1.artemis.service.connectors.SAML2Service;
import de.tum.in.www1.artemis.web.rest.errors.AccessForbiddenException;
import de.tum.in.www1.artemis.web.rest.errors.CaptchaRequiredException;
import de.tum.in.www1.artemis.web.rest.vm.LoginVM;
/**
* Controller to authenticate users.
*/
@RestController
@RequestMapping("/api")
public class UserJWTController {
private static final Logger log = LoggerFactory.getLogger(UserJWTController.class);
private final JWTCookieService jwtCookieService;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
private final Optional<SAML2Service> saml2Service;
public UserJWTController(JWTCookieService jwtCookieService, AuthenticationManagerBuilder authenticationManagerBuilder, Optional<SAML2Service> saml2Service) {
this.jwtCookieService = jwtCookieService;
this.authenticationManagerBuilder = authenticationManagerBuilder;
this.saml2Service = saml2Service;
}
/**
* Authorizes a User
*
* @param loginVM user credentials View Mode
* @param userAgent User Agent
* @param response HTTP response
* @return the ResponseEntity with status 200 (ok), 401 (unauthorized) or 403 (Captcha required)
*/
@PostMapping("/authenticate")
public ResponseEntity<Void> authorize(@Valid @RequestBody LoginVM loginVM, @RequestHeader("User-Agent") String userAgent, HttpServletResponse response) {
var username = loginVM.getUsername();
var password = loginVM.getPassword();
SecurityUtils.checkUsernameAndPasswordValidity(username, password);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
try {
authenticationToken.setDetails(Pair.of("userAgent", userAgent));
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
boolean rememberMe = loginVM.isRememberMe() != null && loginVM.isRememberMe();
ResponseCookie responseCookie = jwtCookieService.buildLoginCookie(rememberMe);
response.addHeader(HttpHeaders.SET_COOKIE, responseCookie.toString());
return ResponseEntity.ok().build();
}
catch (CaptchaRequiredException ex) {
log.warn("CAPTCHA required in JIRA during login for user {}", loginVM.getUsername());
return ResponseEntity.status(HttpStatus.FORBIDDEN).header("X-artemisApp-error", ex.getMessage()).build();
}
}
/**
* Authorizes a User logged in with SAML2
*
* @param body the body of the request. "true" to remember the user.
* @param response HTTP response
* @return the ResponseEntity with status 200 (ok), 401 (unauthorized) or 403 (user not activated)
*/
@PostMapping("/saml2")
public ResponseEntity<Void> authorizeSAML2(@RequestBody final String body, HttpServletResponse response) {
if (saml2Service.isEmpty()) {
throw new AccessForbiddenException("SAML2 is disabled");
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated() || !(authentication.getPrincipal() instanceof final Saml2AuthenticatedPrincipal principal)) {
return new ResponseEntity<>(HttpStatus.UNAUTHORIZED);
}
log.debug("SAML2 authentication: {}", authentication);
try {
authentication = saml2Service.get().handleAuthentication(principal);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
catch (UserNotActivatedException e) {
// If the exception is not caught a 401 is returned.
// That does not match the actual reason and would trigger authentication in the client
return ResponseEntity.status(HttpStatus.FORBIDDEN).header("X-artemisApp-error", e.getMessage()).build();
}
final boolean rememberMe = Boolean.parseBoolean(body);
ResponseCookie responseCookie = jwtCookieService.buildLoginCookie(rememberMe);
response.addHeader(HttpHeaders.SET_COOKIE, responseCookie.toString());
return ResponseEntity.ok().build();
}
/**
* Removes the cookie containing the jwt
*
* @param request HTTP request
* @param response HTTP response
*/
@PostMapping("/logout")
public void logout(HttpServletRequest request, HttpServletResponse response) throws ServletException {
request.logout();
// Logout needs to build the same cookie (secure, httpOnly and sameSite='Lax') or browsers will ignore the header and not unset the cookie
ResponseCookie responseCookie = jwtCookieService.buildLogoutCookie();
response.addHeader(HttpHeaders.SET_COOKIE, responseCookie.toString());
}
}