Skip to content

Commit

Permalink
OAuth / Create user if it does not exist.
Browse files Browse the repository at this point in the history
  • Loading branch information
fxprunayre committed Aug 19, 2024
1 parent d5c9aac commit c612807
Show file tree
Hide file tree
Showing 5 changed files with 128 additions and 4 deletions.
108 changes: 107 additions & 1 deletion src/main/java/org/geonetwork/config/WebSecurityConfiguration.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,25 @@
* This code is licensed under the GPL 2.0 license,
* available at the root application directory.
*/

package org.geonetwork.config;

import static org.geonetwork.security.DatabaseUserDetailsService.GN_AUTHORITY;
import static org.geonetwork.security.DatabaseUserDetailsService.HIGHEST_PROFILE;
import static org.geonetwork.security.DatabaseUserDetailsService.USER_ID;
import static org.geonetwork.security.DatabaseUserDetailsService.USER_NAME;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import org.geonetwork.domain.Profile;
import org.geonetwork.domain.User;
import org.geonetwork.domain.Usergroup;
import org.geonetwork.proxy.HttpProxyPolicyAgentAuthorizationManager;
import org.geonetwork.repository.UserRepository;
import org.geonetwork.repository.UsergroupRepository;
Expand All @@ -17,9 +34,13 @@
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.password.StandardPasswordEncoder;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

Expand All @@ -31,7 +52,9 @@ public class WebSecurityConfiguration {
@Bean
public SecurityFilterChain securityFilterChain(
HttpSecurity http,
HttpProxyPolicyAgentAuthorizationManager proxyPolicyAgentAuthorizationManager)
HttpProxyPolicyAgentAuthorizationManager proxyPolicyAgentAuthorizationManager,
UserRepository userRepository,
UsergroupRepository userGroupRepository)
throws Exception {
http.authorizeHttpRequests(
requests ->
Expand All @@ -44,6 +67,15 @@ public SecurityFilterChain securityFilterChain(
.access(proxyPolicyAgentAuthorizationManager)
.anyRequest()
.permitAll())
.oauth2Login(
oauth ->
oauth
.permitAll()
.userInfoEndpoint(
userInfo ->
userInfo.userAuthoritiesMapper(
this.userOauthAuthoritiesMapper(
userRepository, userGroupRepository))))
.formLogin(
form ->
form.loginPage("/signin")
Expand Down Expand Up @@ -84,4 +116,78 @@ public UserDetailsService userDetailsService(
return new DatabaseUserDetailsService(
checkUsernameOrEmail, passwordEncoder, userRepository, userGroupRepository);
}

private GrantedAuthoritiesMapper userOauthAuthoritiesMapper(
UserRepository userRepository, UsergroupRepository userGroupRepository) {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();

authorities.forEach(
authority -> {
if (authority instanceof OidcUserAuthority) {
// Handle OIDC user authority
} else if (authority instanceof OAuth2UserAuthority oauth2UserAuthority) {
Map<String, Object> userAttributes = oauth2UserAuthority.getAttributes();
String email = userAttributes.get("email").toString();
Optional<User> oauthUser = userRepository.findOptionalByEmail(email);

User user =
oauthUser.orElseGet(
() -> {
String username =
Optional.ofNullable(userAttributes.get("login"))
.orElse(email)
.toString();
User newUser =
User.builder()
.isenabled("y")
.password("")
.username(username)
.name(
Optional.ofNullable(userAttributes.get("name"))
.orElse("")
.toString())
.surname("")
.authtype(authority.getAuthority())
.email(Set.of(email))
.organisation(
Optional.ofNullable(userAttributes.get("company"))
.orElse("")
.toString())
.build();
userRepository.save(newUser);
return newUser;
});

user.setUsername(
Optional.ofNullable(userAttributes.get("name")).orElse(email).toString());
userRepository.save(user);

String mainUserProfile = user.getProfile().name();
List<Usergroup> userGroups = userGroupRepository.findAllByUserid_Id(user.getId());

Map<String, List<Integer>> attributesToCast =
userGroups.stream()
.collect(
Collectors.groupingBy(
ug -> Profile.values()[ug.getId().getProfile()].name(),
Collectors.mapping(
ug -> ug.getGroupid().getId(), Collectors.toList())));

Map<String, Object> attributes = new HashMap<>(attributesToCast);
attributes.put(USER_ID, user.getId());
attributes.put(USER_NAME, user.getUsername());
attributes.put(HIGHEST_PROFILE, mainUserProfile);
attributes.putIfAbsent(Profile.UserAdmin.name(), Collections.emptyList());
attributes.putIfAbsent(Profile.Reviewer.name(), Collections.emptyList());
attributes.putIfAbsent(Profile.RegisteredUser.name(), Collections.emptyList());
attributes.putIfAbsent(Profile.Editor.name(), Collections.emptyList());

mappedAuthorities.add(new OAuth2UserAuthority(GN_AUTHORITY, attributes));
}
});

return mappedAuthorities;
};
}
}
5 changes: 5 additions & 0 deletions src/main/java/org/geonetwork/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.OneToMany;
import jakarta.persistence.SequenceGenerator;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
Expand All @@ -39,6 +42,8 @@
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_id_gen")
@SequenceGenerator(name = "user_id_gen", sequenceName = "user_id_seq", allocationSize = 1)
@Column(name = "id", nullable = false)
private Integer id;

Expand Down
2 changes: 2 additions & 0 deletions src/main/java/org/geonetwork/repository/UserRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@ public interface UserRepository extends JpaRepository<User, Integer> {

Optional<User> findOptionalByEmailAndAuthtypeIsNull(String email);

Optional<User> findOptionalByEmail(String email);

Optional<User> findOptionalByUsernameOrEmailAndAuthtypeIsNull(String username, String email);
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class DatabaseUserDetailsService extends AbstractUserDetailsAuthenticatio

public static final String HIGHEST_PROFILE = "highest_profile";
public static final String USER_ID = "user_id";
public static final String USER_NAME = "username";
public static final String GN_AUTHORITY = "gn";

@Setter @Getter private DatabaseUserAuthProperties checkUsernameOrEmail;
Expand Down Expand Up @@ -110,6 +111,7 @@ protected UserDetails retrieveUser(
String mainUserProfile = user.get().getProfile().name();
Map<String, Object> attributes = new HashMap<>(attributesToCast);
attributes.put(USER_ID, user.get().getId());
attributes.put(USER_NAME, user.get().getUsername());
attributes.put(HIGHEST_PROFILE, mainUserProfile);
attributes.putIfAbsent(Profile.UserAdmin.name(), Collections.emptyList());
attributes.putIfAbsent(Profile.Reviewer.name(), Collections.emptyList());
Expand Down
15 changes: 12 additions & 3 deletions src/main/java/org/geonetwork/security/MeApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
*/
package org.geonetwork.security;

import static org.geonetwork.security.DatabaseUserDetailsService.USER_NAME;

import java.util.Collections;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -27,10 +31,15 @@ public class MeApi {
@PreAuthorize("permitAll")
public ResponseEntity<Map<String, Object>> user(
@AuthenticationPrincipal AuthenticationPrincipal principal,
Authentication authentication,
@AuthenticationPrincipal UserDetails userDetails) {
String userName = "";
if (principal instanceof OAuth2User) {
userName = ((OAuth2User) principal).getAttribute("name");
if (authentication instanceof OAuth2AuthenticationToken) {
userName =
((OAuth2UserAuthority) authentication.getAuthorities().toArray()[0])
.getAttributes()
.get(USER_NAME)
.toString();
} else if (userDetails != null) {
userName = userDetails.getUsername();
}
Expand Down

0 comments on commit c612807

Please sign in to comment.