Skip to content

Commit

Permalink
[FIXES #2971] MapStore - SSO keycloak kerberos (#356)
Browse files Browse the repository at this point in the history
  • Loading branch information
afabiani committed Jul 10, 2024
1 parent 80fbfa3 commit ca42baf
Show file tree
Hide file tree
Showing 4 changed files with 223 additions and 188 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@

package it.geosolutions.geostore.services.rest.security.keycloak;

import static it.geosolutions.geostore.services.rest.security.oauth2.OAuth2Utils.*;

import it.geosolutions.geostore.core.model.User;
import it.geosolutions.geostore.core.model.UserGroup;
import it.geosolutions.geostore.core.model.enums.GroupReservedNames;
Expand All @@ -37,8 +39,9 @@
import it.geosolutions.geostore.services.UserService;
import it.geosolutions.geostore.services.exception.BadRequestServiceEx;
import it.geosolutions.geostore.services.exception.NotFoundServiceEx;
import java.util.*;
import javax.servlet.http.HttpServletRequest;
import org.apache.logging.log4j.Level;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.keycloak.KeycloakSecurityContext;
Expand All @@ -55,41 +58,24 @@
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static it.geosolutions.geostore.services.rest.security.oauth2.OAuth2Utils.ACCESS_TOKEN_PARAM;
import static it.geosolutions.geostore.services.rest.security.oauth2.OAuth2Utils.REFRESH_TOKEN_PARAM;
import static it.geosolutions.geostore.services.rest.security.oauth2.OAuth2Utils.getRequest;

/**
* GeoStore custom Authentication provider. It is used to map a Keycloak Authentication to a GeoStore Authentication where
* the principal is of type {@link User}.
* GeoStore custom Authentication provider. It is used to map a Keycloak Authentication to a
* GeoStore Authentication where the principal is of type {@link User}.
*/
public class GeoStoreKeycloakAuthProvider implements AuthenticationProvider {

private final static Logger LOGGER = LogManager.getLogger(GeoStoreKeycloakAuthProvider.class);


@Autowired
private UserService userService;

@Autowired
private UserGroupService groupService;

private KeyCloakConfiguration configuration;
private static final Logger LOGGER = LogManager.getLogger(GeoStoreKeycloakAuthProvider.class);
private final KeyCloakConfiguration configuration;
@Autowired private UserService userService;
@Autowired private UserGroupService groupService;

public GeoStoreKeycloakAuthProvider(KeyCloakConfiguration configuration) {
this.configuration = configuration;
}

@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {

public Authentication authenticate(Authentication authentication)
throws AuthenticationException {
KeycloakAuthenticationToken token = (KeycloakAuthenticationToken) authentication;
OidcKeycloakAccount account = token.getAccount();
KeycloakSecurityContext context = account.getKeycloakSecurityContext();
Expand All @@ -98,7 +84,8 @@ public Authentication authenticate(Authentication authentication) throws Authent
String refreshToken = null;
Long expiration = null;
HttpServletRequest request = getRequest();
// set tokens as request attributes so that can made available in a cookie for the frontend on the callback url.
// set tokens as request attributes so that can made available in a cookie for the frontend
// on the callback url.
if (accessToken != null) {
expiration = accessToken.getExp();
if (request != null) request.setAttribute(ACCESS_TOKEN_PARAM, accessToken);
Expand All @@ -108,40 +95,51 @@ public Authentication authenticate(Authentication authentication) throws Authent
if (request != null) request.setAttribute(REFRESH_TOKEN_PARAM, refreshToken);
}


List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
GeoStoreKeycloakAuthoritiesMapper grantedAuthoritiesMapper = new GeoStoreKeycloakAuthoritiesMapper(configuration.getRoleMappings(), configuration.getGroupMappings(), configuration.isDropUnmapped());
GeoStoreKeycloakAuthoritiesMapper grantedAuthoritiesMapper =
new GeoStoreKeycloakAuthoritiesMapper(
configuration.getRoleMappings(),
configuration.getGroupMappings(),
configuration.isDropUnmapped());
for (String role : token.getAccount().getRoles()) {
grantedAuthorities.add(new KeycloakRole(role));
}

// maps authorities to GeoStore Role and UserGroup
Collection<? extends GrantedAuthority> mapped = mapAuthorities(grantedAuthoritiesMapper, grantedAuthorities);
Collection<? extends GrantedAuthority> mapped =
mapAuthorities(grantedAuthoritiesMapper, grantedAuthorities);

KeycloakTokenDetails details = new KeycloakTokenDetails(accessTokenStr, refreshToken, expiration);
KeycloakTokenDetails details =
new KeycloakTokenDetails(accessTokenStr, refreshToken, expiration);
details.setIdToken(context.getIdTokenString());
String username = getUsername(authentication);
Set<UserGroup> keycloakGroups = grantedAuthoritiesMapper != null ? grantedAuthoritiesMapper.getGroups() : new HashSet<>();
Set<UserGroup> keycloakGroups =
grantedAuthoritiesMapper != null
? grantedAuthoritiesMapper.getGroups()
: new HashSet<>();

// if the auto creation of user is set to true from keycloak properties we add the groups as well.
// if the auto creation of user is set to true from keycloak properties we add the groups as
// well.
if (configuration.isAutoCreateUser())
keycloakGroups = importGroups(keycloakGroups, grantedAuthorities);

User user = retrieveUser(username, "", grantedAuthoritiesMapper,keycloakGroups);
User user = retrieveUser(username, "", grantedAuthoritiesMapper, keycloakGroups);
addEveryOne(user.getGroups());
if (user.getRole() == null) {
// no role get the one configured to be default for authenticated users.
// no role gets the one configured to be default for authenticated users.
Role defRole = configuration.getAuthenticatedDefaultRole();
user.setRole(defRole);
}
if (user.getGroups() == null) user.setGroups(new HashSet<>());
PreAuthenticatedAuthenticationToken result = new PreAuthenticatedAuthenticationToken(user, "", mapped);
PreAuthenticatedAuthenticationToken result =
new PreAuthenticatedAuthenticationToken(user, "", mapped);
result.setDetails(details);
return result;
}

private Collection<? extends GrantedAuthority> mapAuthorities(GeoStoreKeycloakAuthoritiesMapper grantedAuthoritiesMapper,
Collection<? extends GrantedAuthority> authorities) {
private Collection<? extends GrantedAuthority> mapAuthorities(
GeoStoreKeycloakAuthoritiesMapper grantedAuthoritiesMapper,
Collection<? extends GrantedAuthority> authorities) {
return grantedAuthoritiesMapper != null
? grantedAuthoritiesMapper.mapAuthorities(authorities)
: authorities;
Expand All @@ -153,14 +151,18 @@ public boolean supports(Class<?> aClass) {
}

/**
* Retrieve the user from db or create a new instance. If {@link KeyCloakConfiguration#isAutoCreateUser()} returns
* true, will insert the user in the db.
* Retrieve the user from db or create a new instance. If {@link
* KeyCloakConfiguration#isAutoCreateUser()} returns true, will insert the user in the db.
*
* @param userName
* @param credentials
* @return
*/
protected User retrieveUser(String userName, String credentials, GeoStoreKeycloakAuthoritiesMapper mapper,Set<UserGroup>groups) {
protected User retrieveUser(
String userName,
String credentials,
GeoStoreKeycloakAuthoritiesMapper mapper,
Set<UserGroup> groups) {
User user = null;
if (userService != null) {
try {
Expand All @@ -178,7 +180,7 @@ protected User retrieveUser(String userName, String credentials, GeoStoreKeycloa
user.setEnabled(true);
Role role = mappedRole(mapper);
user.setRole(role);
if (groups==null) groups=new HashSet<>();
if (groups == null) groups = new HashSet<>();
user.setGroups(groups);
// user not found in db, if configured to insert will insert it.
if (userService != null && configuration.isAutoCreateUser()) {
Expand All @@ -194,60 +196,59 @@ protected User retrieveUser(String userName, String credentials, GeoStoreKeycloa
} else {
Role role = mappedRole(mapper);
// might need to update the role / groups if on keycloak side roles changed.
if (isUpdateUser(user,groups,role)) {
updateRoleAndGroups(role,groups,user);
if (isUpdateUser(user, groups, role)) {
updateRoleAndGroups(role, groups, user);
}
}
return user;
}

// update user groups adding the one not already present and added on keycloak side
private User updateRoleAndGroups(Role role,Set<UserGroup> groups, User user){
user.setRole(role);
try {
for (UserGroup g:user.getGroups()){
if (!groups.stream().anyMatch(group->group.getGroupName().equals(g.getGroupName()))) {
UserGroup newGroup = new UserGroup();
newGroup.setGroupName(g.getGroupName());
newGroup.setId(g.getId());
groups.add(g);
}
private User updateRoleAndGroups(Role role, Set<UserGroup> groups, User user) {
user.setRole(role);
try {
for (UserGroup g : user.getGroups()) {
if (!groups.stream()
.anyMatch(group -> group.getGroupName().equals(g.getGroupName()))) {
UserGroup newGroup = new UserGroup();
newGroup.setGroupName(g.getGroupName());
newGroup.setId(g.getId());
groups.add(g);
}
user.setGroups(groups);
userService.update(new User(user));
user = userService.get(user.getName());
} catch (NotFoundServiceEx | BadRequestServiceEx ex) {
LOGGER.error("Error while updating user role...", ex);
}
user.setGroups(groups);
userService.update(new User(user));
user = userService.get(user.getName());
} catch (NotFoundServiceEx | BadRequestServiceEx ex) {
LOGGER.error("Error while updating user role...", ex);
}
return user;
}

// we only update if new roles were added on keycloak or the role changed
private boolean isUpdateUser(User user, Set<UserGroup> groups, Role mappedRole){
Set<UserGroup> incoming=new HashSet<>(groups);
private boolean isUpdateUser(User user, Set<UserGroup> groups, Role mappedRole) {
Set<UserGroup> incoming = new HashSet<>(groups);
incoming.removeAll(user.getGroups());
if (!incoming.stream().allMatch(g->g.getGroupName().equals(GroupReservedNames.EVERYONE.groupName())))
if (!incoming.stream()
.allMatch(g -> g.getGroupName().equals(GroupReservedNames.EVERYONE.groupName())))
return true;

if (configuration.isAutoCreateUser() && (user.getRole() == null || !user.getRole().equals(mappedRole)))
return true;

return false;

return configuration.isAutoCreateUser()
&& (user.getRole() == null || !user.getRole().equals(mappedRole));
}

private Role mappedRole(GeoStoreKeycloakAuthoritiesMapper mapper) {
Role role = null;
if (mapper != null && mapper.getRole() != null)
role = mapper.getRole();
if (mapper != null && mapper.getRole() != null) role = mapper.getRole();
if (role == null) role = configuration.getAuthenticatedDefaultRole();
if (role == null) role = Role.USER;
return role;
}

private String getUsername(Authentication authentication) {
String username = null;
if (authentication != null && authentication.getDetails() instanceof SimpleKeycloakAccount) {
if (authentication != null
&& authentication.getDetails() instanceof SimpleKeycloakAccount) {
SimpleKeycloakAccount account = (SimpleKeycloakAccount) authentication.getDetails();
AccessToken token = account.getKeycloakSecurityContext().getToken();
if (token != null) username = token.getPreferredUsername();
Expand All @@ -256,7 +257,8 @@ private String getUsername(Authentication authentication) {
return username;
}

private Set<UserGroup> importGroups(Set<UserGroup> mappedGroups, Collection<GrantedAuthority> authorities) {
private Set<UserGroup> importGroups(
Set<UserGroup> mappedGroups, Collection<GrantedAuthority> authorities) {
Set<UserGroup> returnSet = new HashSet<>(mappedGroups.size());
try {
if (mappedGroups == null || mappedGroups.isEmpty()) {
Expand All @@ -276,8 +278,7 @@ private Set<UserGroup> importGroups(Set<UserGroup> mappedGroups, Collection<Gran
return returnSet;
}

private UserGroup importGroup(GrantedAuthority a)
throws BadRequestServiceEx {
private UserGroup importGroup(GrantedAuthority a) throws BadRequestServiceEx {
return importGroup(a.getAuthority());
}

Expand Down Expand Up @@ -308,9 +309,9 @@ public void setGroupService(UserGroupService groupService) {
this.groupService = groupService;
}

private void addEveryOne(Set<UserGroup> groups){
String everyone=GroupReservedNames.EVERYONE.groupName();
if (!groups.stream().anyMatch(g->g.getGroupName().equals(everyone))) {
private void addEveryOne(Set<UserGroup> groups) {
String everyone = GroupReservedNames.EVERYONE.groupName();
if (!groups.stream().anyMatch(g -> g.getGroupName().equals(everyone))) {
UserGroup everyoneGroup = new UserGroup();
everyoneGroup.setEnabled(true);
everyoneGroup.setId(-1L);
Expand Down
Loading

0 comments on commit ca42baf

Please sign in to comment.