Skip to content
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

[Backport to 2.1.x][FIXES #2971] MapStore - SSO keycloak kerberos (#356) #358

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading