Skip to content

Commit

Permalink
#219: improvements to the LDAP compatible DAOs to fully replace the s…
Browse files Browse the repository at this point in the history
…tandard ones in a MapStore standard security configuration (#220)

* #219: improvements to the LDAP compatible DAOs to fully replace the standard ones in a MapStore stadanrd security configuration

* Update src/core/persistence/src/main/java/it/geosolutions/geostore/core/dao/ldap/impl/UserDAOImpl.java

Co-authored-by: Lorenzo Natali <offtherailz@gmail.com>

Co-authored-by: Lorenzo Natali <offtherailz@gmail.com>
  • Loading branch information
mbarto and offtherailz authored Mar 18, 2021
1 parent d22d469 commit 110018b
Show file tree
Hide file tree
Showing 13 changed files with 613 additions and 223 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@
*/
package it.geosolutions.geostore.core.model.enums;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import it.geosolutions.geostore.core.model.UserGroup;

/**
* @author DamianoG
Expand Down Expand Up @@ -51,4 +58,20 @@ public static boolean isAllowedName(String groupNameToCheck){
}
return true;
}

/**
* Utility method to remove Reserved group (for example EVERYONE) from a group list
*
* @param groups
* @return
*/
public static Set<UserGroup> checkReservedGroups(Collection<UserGroup> groups) {
Set<UserGroup> result = new HashSet<UserGroup>();
for(UserGroup ug : groups){
if(GroupReservedNames.isAllowedName(ug.getGroupName())){
result.add(ug);
}
}
return result;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (C) 2021 - 2011 GeoSolutions S.A.S. http://www.geo-solutions.it
*
* GPLv3 + Classpath exception
*
* This program is free software: you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*/
package it.geosolutions.geostore.core.model;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.junit.Test;
import it.geosolutions.geostore.core.model.enums.GroupReservedNames;
import static org.junit.Assert.assertEquals;

public class GroupReservedNamesTest {
@Test
public void testRemoveReserved() {
List<UserGroup> groups = new ArrayList<UserGroup>();
UserGroup everyOne = new UserGroup();
everyOne.setGroupName(GroupReservedNames.EVERYONE.groupName());
groups.add(everyOne);
UserGroup sample = new UserGroup();
sample.setGroupName("sample");
groups.add(sample);

Set<UserGroup> result = GroupReservedNames.checkReservedGroups(groups);

assertEquals(1, result.size());
assertEquals("sample", result.iterator().next().getGroupName());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,18 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.naming.directory.DirContext;

import org.apache.commons.lang.StringUtils;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.ldap.control.SortControlDirContextProcessor;
import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextProcessor;
import org.springframework.ldap.core.LdapTemplate;

import com.googlecode.genericdao.search.Filter;
import com.googlecode.genericdao.search.ISearch;

Expand Down Expand Up @@ -197,19 +201,38 @@ private String getLdapFilter(Filter filter, Map<String, Object> propertyMapper)
mapper = (Map)propertyMapper.get(property);
}
}
// we support the minimum set of operators used by GeoStore user and group services
switch(filter.getOperator()) {
case Filter.OP_EQUAL:
return property + "=" + filter.getValue().toString();
case Filter.OP_SOME:
return getLdapFilter((Filter)filter.getValue(), mapper);
case Filter.OP_ILIKE:
return property + "=" + filter.getValue().toString().replaceAll("[%]", "*");
case Filter.OP_IN:
return getInLdapFilter(property, (List)filter.getValue());
//TODO: implement all operators
}
return "";
}

/**
* Builds a filter for property in (values) search type.
* This is done by creating a list of property=value combined by or (|).
*
* @param property
* @param values
* @return
*/
private String getInLdapFilter(String property, List values) {
List<String> filters = new ArrayList<String>();
for(Object value : values) {
filters.add("(" + property + "=" + value.toString() + ")");
}
return StringUtils.join(filters, "|");
}

/**
* Returns true if the given search has one or more filters on a nested object.
*
* @param search
Expand Down Expand Up @@ -263,7 +286,7 @@ protected ISearch getNestedSearch(ISearch search) {
* @param filter
* @return
*/
private Filter getNestedFilter(Filter filter) {
protected Filter getNestedFilter(Filter filter) {
if (filter.getOperator() == Filter.OP_SOME || filter.getOperator() == Filter.OP_ALL) {
return (Filter)filter.getValue();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,28 @@

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.naming.directory.SearchControls;

import org.springframework.ldap.core.ContextSource;
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DirContextProcessor;
import org.springframework.ldap.core.support.AbstractContextMapper;

import com.googlecode.genericdao.search.Filter;
import com.googlecode.genericdao.search.ISearch;
import com.googlecode.genericdao.search.Search;

import it.geosolutions.geostore.core.dao.UserDAO;
import it.geosolutions.geostore.core.model.User;
import it.geosolutions.geostore.core.model.UserAttribute;
import it.geosolutions.geostore.core.model.UserGroup;
import it.geosolutions.geostore.core.model.enums.Role;

/**
* Class UserDAOImpl.
Expand All @@ -46,6 +54,7 @@
public class UserDAOImpl extends LdapBaseDAOImpl implements UserDAO {
protected Map<String, String> attributesMapper = new HashMap<String, String>();
private Pattern memberPattern = Pattern.compile("^(.*)$");
private String adminRoleGroup = "ADMIN";

UserGroupDAOImpl userGroupDAO = null;

Expand All @@ -60,7 +69,21 @@ public void setUserGroupDAO(UserGroupDAOImpl userGroupDAO) {
}
}

public String getAdminRoleGroup() {
return adminRoleGroup;
}

/**
* Case insensitive name of the group associated to the ADMIN role.
* This is used to assign ADMIN role to users belonging to a specific LDAP group.
*
* @param adminRoleGroup ADMIN role group name (default to ADMIN)
*/
public void setAdminRoleGroup(String adminRoleGroup) {
this.adminRoleGroup = adminRoleGroup;
}

/**
* Sets regular expression used to extract the member user name from a member LDAP attribute.
* The LDAP attribute can contain a DN, so this is useful to extract the real member name from it.
*
Expand Down Expand Up @@ -91,8 +114,10 @@ public void setAttributesMapper(Map<String, String> attributesMapper) {
*/
@Override
public void persist(User... entities) {
throw new UnsupportedOperationException();
// NOT SUPPORTED
// we don't want to throw an exception on write operations, because
// some authentication providers try to persist stuff for synchronization
// purposes and they don't know DAOs can be readonly
// TODO: make readonly behaviour explicit
}

/*
Expand All @@ -114,6 +139,7 @@ public List<User> findAll() {
@Override
public List<User> search(ISearch search) {
if (isNested(search)) {
// users belonging to a group
List<User> users = new ArrayList<User>();
for(UserGroup group : userGroupDAO.search(getNestedSearch(search))) {
users.addAll(group.getUsers());
Expand Down Expand Up @@ -152,21 +178,68 @@ protected User doMapFromContext(DirContextOperations ctx) {
user.setId((long)counter++); // TODO: optionally map an attribute to the id
user.setEnabled(true);
user.setName(ctx.getStringAttribute(nameAttribute));
List<UserAttribute> attributes = new ArrayList<UserAttribute>();
for (String ldapAttr : attributesMapper.keySet()) {
String value = ctx.getStringAttribute(ldapAttr);
String userAttr = attributesMapper.get(ldapAttr);
UserAttribute attr = new UserAttribute();
attr.setName(userAttr);
attr.setValue(value);
attributes.add(attr);
}
user.setAttribute(attributes);
user.setAttribute(fetchAttributes(ctx));
assignGroupsAndRole(ctx, user);
return user;
}

}, processor);
}

/**
* Gets all the attributes defined in AttributeMapper.
*
* @param ctx
* @return
*/
private List<UserAttribute> fetchAttributes(DirContextOperations ctx) {
List<UserAttribute> attributes = new ArrayList<UserAttribute>();
for (String ldapAttr : attributesMapper.keySet()) {
String value = ctx.getStringAttribute(ldapAttr);
String userAttr = attributesMapper.get(ldapAttr);
UserAttribute attr = new UserAttribute();
attr.setName(userAttr);
attr.setValue(value);
attributes.add(attr);
}
return attributes;
}

/**
* If UserGroupDAO is defined, fetches all the groups
* using a membership filter (member=<userDN>) on groups.
*
* Assigns the ADMIN role to users belonging to the adminRoleGroup group.
*
* @param ctx
* @param user
*/
private void assignGroupsAndRole(DirContextOperations ctx, User user) {
// defaults to no groups and USER role
user.setGroups(new HashSet<UserGroup>());
user.setRole(Role.USER);
if (userGroupDAO != null) {
Search searchCriteria = new Search(UserGroup.class);
searchCriteria.addFilterSome("user",
new Filter("name", ctx.getNameInNamespace(), Filter.OP_EQUAL));
for (UserGroup ug : userGroupDAO.search(searchCriteria)) {
if (isAdminGroup(ug)) {
user.setRole(Role.ADMIN);
}
user.getGroups().add(ug);
}

}
}

/**
* Returns true if the given group is the adminRoleGroup group.
*
* @param ug
* @return
*/
private boolean isAdminGroup(UserGroup ug) {
return ug.getGroupName().equalsIgnoreCase(adminRoleGroup);
}

/*
* (non-Javadoc)
Expand All @@ -175,7 +248,11 @@ protected User doMapFromContext(DirContextOperations ctx) {
*/
@Override
public User merge(User entity) {
throw new UnsupportedOperationException();
// we don't want to throw an exception on write operations, because
// some authentication providers try to persist stuff for synchronization
// purposes and they don't know DAOs can be readonly
// TODO: make readonly behaviour explicit
return entity;
}

/*
Expand All @@ -185,7 +262,11 @@ public User merge(User entity) {
*/
@Override
public boolean remove(User entity) {
throw new UnsupportedOperationException();
// we don't want to throw an exception on write operations, because
// some authentication providers try to persist stuff for synchronization
// purposes and they don't know DAOs can be readonly
// TODO: make readonly behaviour explicit
return true;
}

/*
Expand All @@ -195,7 +276,11 @@ public boolean remove(User entity) {
*/
@Override
public boolean removeById(Long id) {
throw new UnsupportedOperationException();
// we don't want to throw an exception on write operations, because
// some authentication providers try to persist stuff for synchronization
// purposes and they don't know DAOs can be readonly
// TODO: make readonly behaviour explicit
return true;
}

/*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DirContextProcessor;
import org.springframework.ldap.core.support.AbstractContextMapper;

import com.googlecode.genericdao.search.Filter;
import com.googlecode.genericdao.search.ISearch;
import it.geosolutions.geostore.core.dao.UserGroupDAO;
import it.geosolutions.geostore.core.model.UserGroup;
Expand Down Expand Up @@ -130,8 +132,16 @@ public UserGroup find(Long id) {
@SuppressWarnings("unchecked")
@Override
public List<UserGroup> search(ISearch search) {
String filter;
if (isNested(search)) {
// membership filter (member = <user>)
Filter nested = getNestedFilter(search.getFilters().get(0));
filter = memberAttribute + "=" + nested.getValue().toString();
} else {
filter = getLdapFilter(search, getPropertyMapper());
}
return addEveryOne(
ldapSearch(combineFilters(baseFilter, getLdapFilter(search, getPropertyMapper())), getProcessorForSearch(search)),
ldapSearch(combineFilters(baseFilter, filter), getProcessorForSearch(search)),
search
);
}
Expand Down
Loading

0 comments on commit 110018b

Please sign in to comment.