This is the codeAbility Sharing Platform! Learn more about the codeAbility Sharing Platform.

Skip to content
Snippets Groups Projects
Commit ffceec5f authored by Michael Breu's avatar Michael Breu :speech_balloon:
Browse files

Working Version

parent 0cf8157a
Branches
Tags
1 merge request!17Initial Merge to Prepare Release 1.0.0
Showing
with 532 additions and 20 deletions
......@@ -224,7 +224,12 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<dependency>
<groupId>org.gitlab4j</groupId>
<artifactId>gitlab4j-api</artifactId>
<version>4.15.7</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
......@@ -306,6 +311,15 @@
<groupId>org.zalando</groupId>
<artifactId>problem-spring-web</artifactId>
</dependency>
<!-- Spring Security OAuth 2.0 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
......@@ -655,6 +669,8 @@
<verbose>true</verbose>
<logging>debug</logging>
<contexts>!test</contexts>
<!-- currently not really relevant ? -->
<diffExcludeObjects>oauth_access_token, oauth_approvals, oauth_client_details, oauth_client_token, oauth_code, oauth_refresh_token</diffExcludeObjects>
</configuration>
<dependencies>
<dependency>
......
package at.ac.uibk.gitsearch.config;
import at.ac.uibk.gitsearch.security.*;
import at.ac.uibk.gitsearch.security.jwt.*;
import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED_VALUE;
import java.net.URI;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.event.EventListener;
import org.springframework.core.convert.converter.Converter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequestEntityConverter;
import org.springframework.security.oauth2.client.oidc.authentication.OidcIdTokenDecoderFactory;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExchange;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
import org.springframework.security.oauth2.jwt.JwtDecoders;
import org.springframework.security.oauth2.jwt.JwtValidators;
import org.springframework.security.oauth2.jwt.MappedJwtClaimSetConverter;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.util.UriComponentsBuilder;
import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;
import at.ac.uibk.gitsearch.domain.User;
import at.ac.uibk.gitsearch.security.AuthoritiesConstants;
import at.ac.uibk.gitsearch.security.jwt.JWTConfigurer;
import at.ac.uibk.gitsearch.security.jwt.TokenProvider;
import at.ac.uibk.gitsearch.security.oauth2.UserDetailsFetcher;
import at.ac.uibk.gitsearch.service.UserService;
import at.ac.uibk.gitsearch.service.dto.UserDTO;
import at.ac.uibk.gitsearch.service.mapper.UserMapper;
import io.github.jhipster.config.JHipsterProperties;
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Import(SecurityProblemSupport.class)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Value("${spring.security.oauth2.client.provider.gitlabOidc.issuer-uri}")
private String gitLabIssuerUri;
private final JHipsterProperties jHipsterProperties;
private final TokenProvider tokenProvider;
private final CorsFilter corsFilter;
private final SecurityProblemSupport problemSupport;
public SecurityConfiguration(TokenProvider tokenProvider, CorsFilter corsFilter, SecurityProblemSupport problemSupport) {
public SecurityConfiguration(TokenProvider tokenProvider, CorsFilter corsFilter,
JHipsterProperties jHipsterProperties, SecurityProblemSupport problemSupport
) {
this.tokenProvider = tokenProvider;
this.corsFilter = corsFilter;
this.problemSupport = problemSupport;
this.jHipsterProperties = jHipsterProperties;
}
// @Autowired
// public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
// auth
// .inMemoryAuthentication()
// .withUser("user").password("password").roles("USER");
// }
/**
* inserts a new authenticated user into Database
* @author Michael Breu
*
*/
@Service
public static class OAuth2AuthenticationSuccessEventHandler{
private final Set<String> defaultAuthorities = new HashSet<String>();
@Autowired
private UserDetailsFetcher userDetailsFetcher;
@Autowired
private UserMapper userMapper;
public OAuth2AuthenticationSuccessEventHandler() {
defaultAuthorities.add(AuthoritiesConstants.USER);
}
@Autowired
private UserService userService;
@EventListener({AuthenticationSuccessEvent.class, InteractiveAuthenticationSuccessEvent.class})
public void processAuthenticationSuccessEvent(AbstractAuthenticationEvent e) {
if (!(e.getAuthentication() instanceof OAuth2LoginAuthenticationToken)) {
return;
}
Object principal = e.getAuthentication().getPrincipal();
if (principal instanceof OidcUser) {
OidcUser oidcUser = (OidcUser) principal;
String email = oidcUser.getEmail();
Optional<User> userO = userService.getUserWithAuthoritiesByEmail(email);
if(userO.isPresent()) {
UserDTO userDto = userMapper.userToUserDTO(userO.get());
boolean hasChanged = userDetailsFetcher.updateUserDetails(userDto, oidcUser, (OAuth2LoginAuthenticationToken) e.getAuthentication());
if (hasChanged)
userService.updateUser(userDto);
} else {
UserDTO u = new UserDTO();
u.setFirstName(oidcUser.getGivenName());
u.setLastName(oidcUser.getFamilyName());
u.setLogin(email);
u.setActivated(true);
u.setLastModifiedBy("system");
u.setCreatedDate(java.time.Instant.now());
u.setAuthorities(defaultAuthorities);
u.setEmail(oidcUser.getEmail());
@SuppressWarnings("unused")
boolean hasChanged = userDetailsFetcher.updateUserDetails(u, oidcUser, (OAuth2LoginAuthenticationToken) e.getAuthentication());
userService.createUser(u);
}
}
}
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
......@@ -56,9 +187,12 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
public void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.csrf()
.disable()
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.authenticationProvider(customJWTauthenticationProvider())
.addFilterBefore(corsFilter, CsrfFilter.class)
// .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling()
.authenticationEntryPoint(problemSupport)
.accessDeniedHandler(problemSupport)
......@@ -74,9 +208,11 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
.deny()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
// We need a stateful session management here!
// .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/oauth2/**").permitAll()
.antMatchers("/api/authenticate").permitAll()
.antMatchers("/api/register").denyAll()
.antMatchers("/api/activate").permitAll()
......@@ -90,11 +226,166 @@ public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
.and()
.httpBasic()
.and()
.apply(securityConfigurerAdapter());
// @formatter:on
.apply(securityConfigurerAdapter())
.and()
.oauth2Login()
.and()
.oauth2ResourceServer() // .bearerTokenResolver(bearerTokenResolver)
.jwt()
.jwtAuthenticationConverter(authenticationConverter())
.and()
// .authenticationManagerResolver(authenticationManagerResolver())
.and()
.oauth2Client();
http.oauth2Login().tokenEndpoint().accessTokenResponseClient(accessTokenResponseClient());
// .apply(securityConfigurerAdapter())
;
// @formatter:on
}
Converter<Jwt, AbstractAuthenticationToken> authenticationConverter() {
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new at.ac.uibk.gitsearch.security.oauth2.JwtGrantedAuthorityConverter());
return jwtAuthenticationConverter;
}
private JWTConfigurer securityConfigurerAdapter() {
return new JWTConfigurer(tokenProvider);
}
public AuthenticationProvider customJWTauthenticationProvider() {
return new AuthenticationProvider() {
@Override
public boolean supports(Class<?> authentication) {
return BearerTokenAuthenticationToken.class.isAssignableFrom(authentication);
}
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
if(authentication.isAuthenticated()) return authentication;
if (authentication instanceof BearerTokenAuthenticationToken) {
BearerTokenAuthenticationToken bearerToken = (BearerTokenAuthenticationToken) authentication;
if(tokenProvider.validateToken(bearerToken.getToken())) {
Authentication success = tokenProvider.getAuthentication(bearerToken.getToken());
// success.setAuthenticated(true);
return success;
}
}
return null;
}
};
}
/**
* not used by OidcAuthorizationCodeAuthenticationProvider :-(
* @return
*/
@Bean
JwtDecoder jwtDecoder() {
NimbusJwtDecoder oidcJwtDecoder = (NimbusJwtDecoder) JwtDecoders.fromOidcIssuerLocation(gitLabIssuerUri);
OAuth2TokenValidator<Jwt> audienceValidator = new at.ac.uibk.gitsearch.security.oauth2.AudienceValidator(jHipsterProperties.getSecurity().getOauth2().getAudience());
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(gitLabIssuerUri);
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
// TODO with Audience ist merkwürdig/unnötig
oidcJwtDecoder.setJwtValidator(withAudience);
return oidcJwtDecoder;
// return new CustomJwtDecoder(oidcJwtDecoder, tokenProvider);
}
// @Bean
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient(){
DefaultAuthorizationCodeTokenResponseClient accessTokenResponseClient =
new DefaultAuthorizationCodeTokenResponseClient();
accessTokenResponseClient.setRequestEntityConverter(new CustomOAuth2AuthorizationCodeGrantRequestEntityConverter());
return accessTokenResponseClient;
}
public static class CustomOAuth2AuthorizationCodeGrantRequestEntityConverter extends OAuth2AuthorizationCodeGrantRequestEntityConverter {
/**
* Returns the {@link RequestEntity} used for the Access Token Request.
*
* @param authorizationCodeGrantRequest the authorization code grant request
* @return the {@link RequestEntity} used for the Access Token Request
*/
@Override
public RequestEntity<?> convert(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
ClientRegistration clientRegistration = authorizationCodeGrantRequest.getClientRegistration();
HttpHeaders headers = getTokenRequestHeaders(clientRegistration);
MultiValueMap<String, String> formParameters = this.buildFormParameters(authorizationCodeGrantRequest);
URI uri = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getTokenUri())
.build()
.toUri();
return new RequestEntity<>(formParameters, headers, HttpMethod.POST, uri);
}
static HttpHeaders getTokenRequestHeaders(ClientRegistration clientRegistration) {
HttpHeaders headers = new HttpHeaders();
headers.addAll(getDefaultTokenRequestHeaders());
// if (ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
// headers.setBasicAuth(clientRegistration.getClientId(), clientRegistration.getClientSecret());
// }
return headers;
}
private static HttpHeaders getDefaultTokenRequestHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8));
final MediaType contentType = MediaType.valueOf(APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8");
headers.setContentType(contentType);
return headers;
}
/**
* Returns a {@link MultiValueMap} of the form parameters used for the Access Token Request body.
*
* @param authorizationCodeGrantRequest the authorization code grant request
* @return a {@link MultiValueMap} of the form parameters used for the Access Token Request body
*/
private MultiValueMap<String, String> buildFormParameters(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
ClientRegistration clientRegistration = authorizationCodeGrantRequest.getClientRegistration();
OAuth2AuthorizationExchange authorizationExchange = authorizationCodeGrantRequest.getAuthorizationExchange();
MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
formParameters.add(OAuth2ParameterNames.GRANT_TYPE, authorizationCodeGrantRequest.getGrantType().getValue());
formParameters.add(OAuth2ParameterNames.CODE, authorizationExchange.getAuthorizationResponse().getCode());
String redirectUri = authorizationExchange.getAuthorizationRequest().getRedirectUri();
String codeVerifier = authorizationExchange.getAuthorizationRequest().getAttribute(PkceParameterNames.CODE_VERIFIER);
if (redirectUri != null) {
formParameters.add(OAuth2ParameterNames.REDIRECT_URI, redirectUri);
}
formParameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
// if (!ClientAuthenticationMethod.BASIC.equals(clientRegistration.getClientAuthenticationMethod())) {
// formParameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
// }
// if (ClientAuthenticationMethod.POST.equals(clientRegistration.getClientAuthenticationMethod())) {
// formParameters.add(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
// }
if (codeVerifier != null) {
formParameters.add(PkceParameterNames.CODE_VERIFIER, codeVerifier);
}
return formParameters;
}
}
}
......@@ -55,7 +55,7 @@ public class CustomAuditEventRepository implements AuditEventRepository {
!Constants.ANONYMOUS_USER.equals(event.getPrincipal())) {
PersistentAuditEvent persistentAuditEvent = new PersistentAuditEvent();
persistentAuditEvent.setPrincipal(event.getPrincipal());
persistentAuditEvent.setPrincipal(truncatePrincipal(event.getPrincipal()));
persistentAuditEvent.setAuditEventType(event.getType());
persistentAuditEvent.setAuditEventDate(event.getTimestamp());
Map<String, String> eventData = auditEventConverter.convertDataToStrings(event.getData());
......@@ -64,6 +64,16 @@ public class CustomAuditEventRepository implements AuditEventRepository {
}
}
/**
* an OAuth2-Principal can get very long, so we truncate it here to 50 characters.
*/
private String truncatePrincipal(String principal) {
if(principal==null) return null;
if (principal.length() <= 50) // TODO make a constant
return principal;
else return principal.substring(0,50);
}
/**
* Truncate event data that might exceed column length.
*/
......
package at.ac.uibk.gitsearch.security;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Optional;
import java.util.stream.Stream;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
/**
* Utility class for Spring Security.
......@@ -33,6 +42,17 @@ public final class SecurityUtils {
} else if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
return springSecurityUser.getUsername();
} else if (authentication instanceof JwtAuthenticationToken) {
return (String) ((JwtAuthenticationToken)authentication).getToken().getClaims().get("preferred_username");
} else if (authentication instanceof OAuth2AuthenticationToken) {
// we use email for username!
return ((OidcUser) ((OAuth2AuthenticationToken) authentication).getPrincipal()).getEmail();
} else if (authentication.getPrincipal() instanceof OidcUser) {
return ((OidcUser) authentication.getPrincipal()).getName();
// Map<String, Object> attributes = ((DefaultOidcUser) authentication.getPrincipal()).getAttributes();
// if (attributes.containsKey("preferred_username")) {
// return (String) attributes.get("preferred_username");
// }
} else if (authentication.getPrincipal() instanceof String) {
return (String) authentication.getPrincipal();
}
......@@ -78,8 +98,27 @@ public final class SecurityUtils {
}
private static Stream<String> getAuthorities(Authentication authentication) {
return authentication.getAuthorities().stream()
Collection<? extends GrantedAuthority> authorities = authentication instanceof JwtAuthenticationToken ?
extractAuthorityFromClaims(((JwtAuthenticationToken) authentication).getToken().getClaims())
: authentication.getAuthorities();
return authorities.stream()
.map(GrantedAuthority::getAuthority);
}
public static List<GrantedAuthority> extractAuthorityFromClaims(Map<String, Object> claims) {
return mapRolesToGrantedAuthorities(getRolesFromClaims(claims));
}
@SuppressWarnings("unchecked")
private static Collection<String> getRolesFromClaims(Map<String, Object> claims) {
return (Collection<String>) claims.getOrDefault("groups",
claims.getOrDefault("roles", new ArrayList<>()));
}
private static List<GrantedAuthority> mapRolesToGrantedAuthorities(Collection<String> roles) {
return roles.stream()
.filter(role -> role.startsWith("ROLE_"))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
}
package at.ac.uibk.gitsearch.security.oauth2;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.util.Assert;
import java.util.List;
public class AudienceValidator implements OAuth2TokenValidator<Jwt> {
private final Logger log = LoggerFactory.getLogger(AudienceValidator.class);
private final OAuth2Error error = new OAuth2Error("invalid_token", "The required audience is missing", null);
private final List<String> allowedAudience;
public AudienceValidator(List<String> allowedAudience) {
Assert.notEmpty(allowedAudience, "Allowed audience should not be null or empty.");
this.allowedAudience = allowedAudience;
}
public OAuth2TokenValidatorResult validate(Jwt jwt) {
List<String> audience = jwt.getAudience();
if (audience.stream().anyMatch(allowedAudience::contains)) {
return OAuth2TokenValidatorResult.success();
} else {
log.warn("Invalid audience: {}", audience);
return OAuth2TokenValidatorResult.failure(error);
}
}
}
package at.ac.uibk.gitsearch.security.oauth2;
import at.ac.uibk.gitsearch.security.SecurityUtils;
import java.util.Collection;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Component;
@Component
public class JwtGrantedAuthorityConverter implements Converter<Jwt, Collection<GrantedAuthority>> {
public JwtGrantedAuthorityConverter() {
// Bean extracting authority.
}
@Override
public Collection<GrantedAuthority> convert(Jwt jwt) {
return SecurityUtils.extractAuthorityFromClaims(jwt.getClaims());
}
}
package at.ac.uibk.gitsearch.security.oauth2;
import java.time.Instant;
import java.util.function.Consumer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.gitlab4j.api.Constants.TokenType;
import org.gitlab4j.api.GitLabApi;
import org.gitlab4j.api.GitLabApiException;
import org.gitlab4j.api.models.User;
import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.stereotype.Component;
import at.ac.uibk.gitsearch.service.dto.UserDTO;
@Component
public class UserDetailsFetcher {
private final static Logger logger = LogManager.getLogger(UserDetailsFetcher.class);
/** fills in data from OIDC repository.
* returns true, if user data really changed **/
public boolean updateUserDetails(UserDTO u, OidcUser oidcUser, OAuth2LoginAuthenticationToken accessToken) {
String idToken = accessToken.getAccessToken().getTokenValue();
ProviderDetails providerDetails = accessToken.getClientRegistration().getProviderDetails();
String issuerURI = (String) providerDetails.getConfigurationMetadata().get("issuer");
try( GitLabApi gitLabApi = new GitLabApi(issuerURI, TokenType.OAUTH2_ACCESS, idToken)) {
boolean modified = false;
gitLabApi.enableRequestResponseLogging();
// List<Project> memberProjects = gitLabApi.getProjectApi().getMemberProjects();
User gitUser = gitLabApi.getUserApi().getCurrentUser();
modified |= updateAttribute(gitUser.getAvatarUrl(),u.getImageUrl(), u::setImageUrl);
modified |= updateAttribute(gitUser.getEmail(), u.getEmail(), u::setEmail);
modified |= updateAttribute(gitUser.getName(),u.getLastName(), u::setLastName);
// modified |= updateAttribute(gitUser.getUsername(),u.getLogin(), u::setLogin);
if (modified) u.setLastModifiedDate(Instant.now());
return modified;
} catch (GitLabApiException e) {
logger.warn("Cannot fetch details for user {}", oidcUser.getName());
}
return false;
}
private static boolean updateAttribute (String newString, String oldString, Consumer<String> setter) {
if(oldString==null) {
if(newString == null) return false;
} else if(oldString.equals(newString)) {
return false;
}
setter.accept(newString);
return true;
}
}
......@@ -274,6 +274,11 @@ public class UserService {
return userRepository.findOneWithAuthoritiesByLogin(login);
}
@Transactional(readOnly = true)
public Optional<User> getUserWithAuthoritiesByEmail(String email) {
return userRepository.findOneWithAuthoritiesByEmailIgnoreCase(email);
}
@Transactional(readOnly = true)
public Optional<User> getUserWithAuthorities() {
return SecurityUtils.getCurrentUserLogin().flatMap(userRepository::findOneWithAuthoritiesByLogin);
......
......@@ -33,7 +33,7 @@ spring:
#- tls
devtools:
restart:
enabled: true
enabled: false
additional-exclude: static/**,.h2.server.properties
livereload:
enabled: false # we use Webpack dev server + BrowserSync for livereload
......@@ -71,6 +71,16 @@ spring:
cache-duration: PT1S # 1 second, see the ISO 8601 standard
thymeleaf:
cache: false
security:
oauth2:
client:
provider:
gitlabOidc:
issuer-uri: https://sharing.codeability-austria.uibk.ac.at
registration:
gitlabOidc:
client-id: 149276ac11138d9ba72fb3cd12815e3fa2f372866df0eac0f7d1aae5fdffea24
client-secret: 6f480635241f420a361581f4837594ea6f48f5ee6f515c1aa89f325dd922dbb0
server:
port: 8080
......@@ -95,6 +105,10 @@ jhipster:
allow-credentials: true
max-age: 1800
security:
oauth2:
# TODO: audience seems not really relevant, could be omitted? It is identical with client-id above
audience:
- 149276ac11138d9ba72fb3cd12815e3fa2f372866df0eac0f7d1aae5fdffea24
authentication:
jwt:
# This token must be encoded using Base64 and be at least 256 bits long (you can type `openssl rand -base64 64` on your command line to generate a 512 bits one)
......
......@@ -22,7 +22,7 @@ management:
include: ['configprops', 'env', 'health', 'info', 'jhimetrics', 'logfile', 'loggers', 'prometheus', 'threaddump']
endpoint:
health:
show-details: when_authorized
show-details: WHEN_AUTHORIZED
roles: 'ROLE_ADMIN'
jhimetrics:
enabled: true
......
id;login;password_hash;first_name;last_name;email;image_url;activated;lang_key;created_by;last_modified_by
1;system;$2a$10$mE.qmcV0mFU5NcKh73TZx.z4ueI/.bDWbj0T1BYyqP481kGGarKLG;System;System;system@localhost;;true;en;system;system
2;anonymoususer;$2a$10$j8S5d7Sr7.8VTOYNviDPOeWX8KcYILUVJBsYV83Y5NtECayypx9lO;Anonymous;User;anonymous@localhost;;true;en;system;system
3;admin;$2a$10$f3bajqkMQSr47/Nzq2sknuO6KTvXG.3BwJk4VryCBb7shnQv7QeG2;Administrator;Administrator;admin@localhost;;true;en;system;system
3;admin;$2y$10$LATI1Wuc1cpaMXZnNkI1Nes2coKcVHItlXbHFfoJHtDd3KC7sSEEG;Administrator;Administrator;admin@localhost;;true;en;system;system
4;user;$2a$10$WD76nMitzuppCxh5Wl97Hu5unAf2D1ZUedzq75FYf5XpCV8olobba;User;User;user@localhost;;true;en;system;system
......@@ -2,6 +2,14 @@
<div class="col-md-12">
<h1 class="display-4" jhiTranslate="home.title">Welcome!</h1>
<div [ngSwitch]="isAuthenticated()">
<div class="alert alert-success" *ngSwitchCase="true">
<span id="home-logged-message" *ngIf="account" jhiTranslate="home.logged.message"
[translateValues]="{ username: account.login }">You are logged in as user "{{ account.login }}".</span>
</div>
</div>
<div>
<span jhiTranslate="global.messages.info.sharing.platform">
The CodeAbility Sharing Platform provides an infrastructure that enables the exchange of learning content in the field of programming. The content includes programming exercises, lecture materials, and collections of links on all topics related to the acquisition of programming skills. This search engine provides a quick and easy way for users to search for content published on the sharing platform. The search results can be further restricted by various filter options such as licenses, programming languages, and authors.
......
......@@ -37,6 +37,8 @@
<button type="submit" class="btn btn-primary" jhiTranslate="login.form.button">Sign in</button>
</form>
<br/>
<button type="submit" class="btn btn-primary" (click)="loginWithGitLab()">Login With GitLab</button>
<div class="mt-3 alert alert-warning">
<a class="alert-link" (click)="requestResetPassword()" jhiTranslate="login.password.forgot">Did you forget your password?</a>
......
......@@ -3,6 +3,8 @@ import { FormBuilder } from '@angular/forms';
import { NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { Router } from '@angular/router';
import { Location } from '@angular/common';
import { LoginService } from 'app/core/login/login.service';
@Component({
......@@ -21,7 +23,7 @@ export class LoginModalComponent implements AfterViewInit {
rememberMe: [false],
});
constructor(private loginService: LoginService, private router: Router, public activeModal: NgbActiveModal, private fb: FormBuilder) {}
constructor(private loginService: LoginService, private router: Router, public activeModal: NgbActiveModal, private fb: FormBuilder, private location: Location) {}
ngAfterViewInit(): void {
if (this.username) {
......@@ -66,6 +68,13 @@ export class LoginModalComponent implements AfterViewInit {
this.router.navigate(['/account/register']);
}
loginWithGitLab(): void {
// If you have configured multiple OIDC providers, then, you can update this URL to /login.
// It will show a Spring Security generated login page with links to configured OIDC providers.
location.href = `${location.origin}${this.location.prepareExternalUrl('oauth2/authorization/gitlabOidc')}`;
}
requestResetPassword(): void {
this.activeModal.dismiss('to state requestReset');
this.router.navigate(['/account/reset', 'request']);
......
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment