-
Notifications
You must be signed in to change notification settings - Fork 292
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Programming exercises
: Provide theia clone information on redirect
#9379
base: feature/re-key
Are you sure you want to change the base?
Changes from 28 commits
a0497f3
2ab444e
389f5fd
7ce3a49
4160302
eae2c89
0f18104
486b2c5
d9bf634
a643b42
34a84d7
4f1a99b
4aa5171
7abb02c
b7ebea0
685a483
e8be59c
575379b
2d2a5b7
6207ce8
a6cc06e
f82d6fb
beb3d48
625216e
b42126a
63f9cca
a8ec277
a140b11
d3357f8
4d34faa
9ad7a35
d9a1f91
3bff129
25146ba
bfebf27
7a18571
e64adb2
c9f0c0d
0d01a40
64f95cd
6e37a8f
bf2fbbd
cec3866
afa8543
e89f035
395e8df
0cc6620
5a3ae27
43446ae
f75b379
36a182b
110d047
b932844
d5e3b70
de36813
8205b58
7876792
2946389
c7c5322
9b0bcc3
c907b0a
65bc890
749b301
f2d7aaa
f485527
1b50466
70ccfc3
054476c
d3817c8
383945a
ac87b07
10648b0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2,6 +2,7 @@ | |||||||||||
|
||||||||||||
import java.io.IOException; | ||||||||||||
|
||||||||||||
import jakarta.annotation.Nullable; | ||||||||||||
import jakarta.servlet.FilterChain; | ||||||||||||
import jakarta.servlet.ServletException; | ||||||||||||
import jakarta.servlet.ServletRequest; | ||||||||||||
|
@@ -31,26 +32,70 @@ public JWTFilter(TokenProvider tokenProvider) { | |||||||||||
@Override | ||||||||||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { | ||||||||||||
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest; | ||||||||||||
Cookie jwtCookie = WebUtils.getCookie(httpServletRequest, JWT_COOKIE_NAME); | ||||||||||||
if (isJwtCookieValid(this.tokenProvider, jwtCookie)) { | ||||||||||||
Authentication authentication = this.tokenProvider.getAuthentication(jwtCookie.getValue()); | ||||||||||||
|
||||||||||||
String jwtToken = extractValidJwt(httpServletRequest, this.tokenProvider); | ||||||||||||
if (jwtToken != null) { | ||||||||||||
Authentication authentication = this.tokenProvider.getAuthentication(jwtToken); | ||||||||||||
SecurityContextHolder.getContext().setAuthentication(authentication); | ||||||||||||
} | ||||||||||||
|
||||||||||||
filterChain.doFilter(servletRequest, servletResponse); | ||||||||||||
} | ||||||||||||
|
||||||||||||
/** | ||||||||||||
* Checks if the cookie containing the jwt is valid | ||||||||||||
* Extracts the first valid jwt found in the cookie or the Authorization header | ||||||||||||
* | ||||||||||||
* @param tokenProvider the artemis token provider used to generate and validate jwt's | ||||||||||||
* @param jwtCookie the cookie containing the jwt | ||||||||||||
* @return true if the jwt is valid, false if missing or invalid | ||||||||||||
* @param httpServletRequest the http request | ||||||||||||
* @param tokenProvider the Artemis token provider used to generate and validate jwt's | ||||||||||||
* @return the valid jwt or null if not found or invalid | ||||||||||||
*/ | ||||||||||||
public static @Nullable String extractValidJwt(HttpServletRequest httpServletRequest, TokenProvider tokenProvider) { | ||||||||||||
String jwtToken = getJwtFromCookie(WebUtils.getCookie(httpServletRequest, JWT_COOKIE_NAME)); | ||||||||||||
if (isJwtValid(tokenProvider, jwtToken)) { | ||||||||||||
return jwtToken; | ||||||||||||
} | ||||||||||||
jwtToken = getJwtFromBearer(httpServletRequest.getHeader("Authorization")); | ||||||||||||
if (isJwtValid(tokenProvider, jwtToken)) { | ||||||||||||
return jwtToken; | ||||||||||||
} | ||||||||||||
return null; | ||||||||||||
} | ||||||||||||
iyannsch marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
|
||||||||||||
/** | ||||||||||||
* Extracts the jwt from the cookie | ||||||||||||
* | ||||||||||||
* @param jwtCookie the cookie with Key "jwt" | ||||||||||||
* @return the jwt or null if not found | ||||||||||||
*/ | ||||||||||||
public static boolean isJwtCookieValid(TokenProvider tokenProvider, Cookie jwtCookie) { | ||||||||||||
private static @Nullable String getJwtFromCookie(Cookie jwtCookie) { | ||||||||||||
if (jwtCookie == null) { | ||||||||||||
return false; | ||||||||||||
return null; | ||||||||||||
} | ||||||||||||
String jwt = jwtCookie.getValue(); | ||||||||||||
return StringUtils.hasText(jwt) && tokenProvider.validateTokenForAuthority(jwt); | ||||||||||||
return jwtCookie.getValue(); | ||||||||||||
} | ||||||||||||
|
||||||||||||
/** | ||||||||||||
* Extracts the jwt from the Authorization header | ||||||||||||
* | ||||||||||||
* @param jwtBearer the content of the Authorization header | ||||||||||||
* @return the jwt or null if not found | ||||||||||||
*/ | ||||||||||||
private static @Nullable String getJwtFromBearer(String jwtBearer) { | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||
if (!StringUtils.hasText(jwtBearer) || !jwtBearer.startsWith("Bearer ")) { | ||||||||||||
return null; | ||||||||||||
} | ||||||||||||
|
||||||||||||
return jwtBearer.substring(7).trim(); | ||||||||||||
iyannsch marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
} | ||||||||||||
|
||||||||||||
/** | ||||||||||||
* Checks if the jwt is valid | ||||||||||||
* | ||||||||||||
* @param tokenProvider the Artemis token provider used to generate and validate jwt's | ||||||||||||
* @param jwtToken the jwt | ||||||||||||
* @return true if the jwt is valid, false if missing or invalid | ||||||||||||
*/ | ||||||||||||
private static boolean isJwtValid(TokenProvider tokenProvider, String jwtToken) { | ||||||||||||
return StringUtils.hasText(jwtToken) && tokenProvider.validateTokenForAuthority(jwtToken); | ||||||||||||
} | ||||||||||||
} |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -2,6 +2,9 @@ | |||||
|
||||||
import static de.tum.cit.aet.artemis.core.config.Constants.PROFILE_CORE; | ||||||
|
||||||
import java.time.Duration; | ||||||
import java.util.Date; | ||||||
iyannsch marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
import java.util.Map; | ||||||
import java.util.Optional; | ||||||
|
||||||
import jakarta.servlet.ServletException; | ||||||
|
@@ -27,14 +30,18 @@ | |||||
import org.springframework.web.bind.annotation.RequestBody; | ||||||
import org.springframework.web.bind.annotation.RequestHeader; | ||||||
import org.springframework.web.bind.annotation.RequestMapping; | ||||||
import org.springframework.web.bind.annotation.RequestParam; | ||||||
import org.springframework.web.bind.annotation.RestController; | ||||||
|
||||||
import de.tum.cit.aet.artemis.core.dto.vm.LoginVM; | ||||||
import de.tum.cit.aet.artemis.core.exception.AccessForbiddenException; | ||||||
import de.tum.cit.aet.artemis.core.security.SecurityUtils; | ||||||
import de.tum.cit.aet.artemis.core.security.UserNotActivatedException; | ||||||
import de.tum.cit.aet.artemis.core.security.annotations.EnforceAtLeastStudent; | ||||||
import de.tum.cit.aet.artemis.core.security.annotations.EnforceNothing; | ||||||
import de.tum.cit.aet.artemis.core.security.jwt.JWTCookieService; | ||||||
import de.tum.cit.aet.artemis.core.security.jwt.JWTFilter; | ||||||
import de.tum.cit.aet.artemis.core.security.jwt.TokenProvider; | ||||||
import de.tum.cit.aet.artemis.core.service.connectors.SAML2Service; | ||||||
|
||||||
/** | ||||||
|
@@ -49,12 +56,15 @@ public class PublicUserJwtResource { | |||||
|
||||||
private final JWTCookieService jwtCookieService; | ||||||
|
||||||
private final TokenProvider tokenProvider; | ||||||
|
||||||
private final AuthenticationManager authenticationManager; | ||||||
|
||||||
private final Optional<SAML2Service> saml2Service; | ||||||
|
||||||
public PublicUserJwtResource(JWTCookieService jwtCookieService, AuthenticationManager authenticationManager, Optional<SAML2Service> saml2Service) { | ||||||
public PublicUserJwtResource(JWTCookieService jwtCookieService, TokenProvider tokenProvider, AuthenticationManager authenticationManager, Optional<SAML2Service> saml2Service) { | ||||||
this.jwtCookieService = jwtCookieService; | ||||||
this.tokenProvider = tokenProvider; | ||||||
this.authenticationManager = authenticationManager; | ||||||
this.saml2Service = saml2Service; | ||||||
} | ||||||
|
@@ -69,7 +79,7 @@ public PublicUserJwtResource(JWTCookieService jwtCookieService, AuthenticationMa | |||||
*/ | ||||||
@PostMapping("authenticate") | ||||||
@EnforceNothing | ||||||
public ResponseEntity<Void> authorize(@Valid @RequestBody LoginVM loginVM, @RequestHeader("User-Agent") String userAgent, HttpServletResponse response) { | ||||||
public ResponseEntity<Map<String, String>> authorize(@Valid @RequestBody LoginVM loginVM, @RequestHeader("User-Agent") String userAgent, HttpServletResponse response) { | ||||||
|
||||||
var username = loginVM.getUsername(); | ||||||
var password = loginVM.getPassword(); | ||||||
|
@@ -86,14 +96,44 @@ public ResponseEntity<Void> authorize(@Valid @RequestBody LoginVM loginVM, @Requ | |||||
ResponseCookie responseCookie = jwtCookieService.buildLoginCookie(rememberMe); | ||||||
response.addHeader(HttpHeaders.SET_COOKIE, responseCookie.toString()); | ||||||
|
||||||
return ResponseEntity.ok().build(); | ||||||
return ResponseEntity.ok(Map.of("access_token", responseCookie.getValue())); | ||||||
iyannsch marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
catch (BadCredentialsException ex) { | ||||||
log.warn("Wrong credentials during login for user {}", loginVM.getUsername()); | ||||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); | ||||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* Sends a theia token back as cookie and bearer token | ||||||
* | ||||||
* @param request HTTP request | ||||||
* @param response HTTP response | ||||||
* @return the ResponseEntity with status 200 (ok), 401 (unauthorized) | ||||||
*/ | ||||||
iyannsch marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
@PostMapping("theia-token") | ||||||
@EnforceAtLeastStudent | ||||||
public ResponseEntity<String> getTheiaToken(@RequestParam(name = "as-cookie", defaultValue = "false") boolean asCookie, HttpServletRequest request, | ||||||
HttpServletResponse response) { | ||||||
iyannsch marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
// remaining time in milliseconds | ||||||
var jwtToken = JWTFilter.extractValidJwt(request, tokenProvider); | ||||||
if (jwtToken == null) { | ||||||
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build(); | ||||||
} | ||||||
|
||||||
// get validity of the token | ||||||
long tokenRemainingTime = tokenProvider.getExpirationDate(jwtToken).getTime() - new Date().getTime(); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
We use zoned date time almost everywhere else in Artemis, should we also do it here? I'm not sure There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the TokenProvider.java also uses Date() so we should be consistent with this class as this generates the token |
||||||
|
||||||
// 1 day validity | ||||||
long maxDuration = Duration.ofDays(1).toMillis(); | ||||||
ResponseCookie responseCookie = jwtCookieService.buildTheiaCookie(Math.min(tokenRemainingTime, maxDuration)); | ||||||
|
||||||
if (asCookie) { | ||||||
response.addHeader(HttpHeaders.SET_COOKIE, responseCookie.toString()); | ||||||
} | ||||||
return ResponseEntity.ok(responseCookie.getValue()); | ||||||
iyannsch marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
} | ||||||
iyannsch marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
/** | ||||||
* Authorizes a User logged in with SAML2 | ||||||
* | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.