Paulo Gustavo Veiga 2023-11-18 23:22:25 -08:00
parent ab15fb2d36
commit 07e8259417
14 changed files with 259 additions and 201 deletions

View File

@ -13,8 +13,8 @@
</parent>
<properties>
<org.springframework.version>6.0.13</org.springframework.version>
<org.springframework.addons>6.1.2</org.springframework.addons>
<org.springframework.version>6.0.14</org.springframework.version>
<org.springframework.addons>6.1.5</org.springframework.addons>
<spring-security-taglibs.version>6.0.2</spring-security-taglibs.version>
</properties>
@ -147,7 +147,6 @@
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${org.springframework.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>

View File

@ -0,0 +1,35 @@
package com.wisemapping.config;
import com.wisemapping.security.MapAccessPermissionEvaluation;
import com.wisemapping.security.ReadSecurityAdvise;
import com.wisemapping.security.UpdateSecurityAdvise;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
@Configuration
@EnableMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
public class MethodSecurityConfig {
@Autowired
private ReadSecurityAdvise readAdvice;
@Autowired
private UpdateSecurityAdvise updateAdvice;
@Bean
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler =
new DefaultMethodSecurityExpressionHandler();
final MapAccessPermissionEvaluation permissionEvaluator = new MapAccessPermissionEvaluation(readAdvice, updateAdvice);
expressionHandler.setPermissionEvaluator(permissionEvaluator);
return expressionHandler;
}
}

View File

@ -1,6 +1,7 @@
package com.wisemapping.config;
import com.wisemapping.security.AuthenticationSuccessHandler;
import com.wisemapping.security.MapAccessPermissionEvaluation;
import com.wisemapping.security.UserDetailsService;
import com.wisemapping.service.UserService;
import org.jetbrains.annotations.NotNull;
@ -9,6 +10,9 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
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.config.annotation.web.configurers.AbstractHttpConfigurer;

View File

@ -0,0 +1,6 @@
package com.wisemapping.security;
public enum MapAccessPermission {
READ,
WRITE
}

View File

@ -0,0 +1,92 @@
package com.wisemapping.security;
import com.wisemapping.model.Collaborator;
import com.wisemapping.model.Mindmap;
import com.wisemapping.model.User;
import jakarta.validation.constraints.NotNull;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import java.io.Serializable;
public class MapAccessPermissionEvaluation implements PermissionEvaluator {
final private static Logger logger = LogManager.getLogger();
private MapPermissionsSecurityAdvice readAdvice;
private MapPermissionsSecurityAdvice updateAdvice;
public MapAccessPermissionEvaluation(final @NotNull MapPermissionsSecurityAdvice readAdvice, final @NotNull MapPermissionsSecurityAdvice updateAdvice) {
this.readAdvice = readAdvice;
this.updateAdvice = updateAdvice;
}
@Override
public boolean hasPermission(
@NotNull Authentication auth, @NotNull Object targetDomainObject, @NotNull Object permission) {
logger.log(Level.DEBUG, "auth: " + auth + ",targetDomainObject:" + targetDomainObject + ",permission:" + permission);
if ((auth == null) || (targetDomainObject == null) || !(permission instanceof String)) {
logger.debug("Permissions could not be validated, illegal parameters.");
return false;
}
boolean result;
final User user = Utils.getUser();
final MapAccessPermission perm = MapAccessPermission.valueOf((permission.toString().toUpperCase()));
if (targetDomainObject instanceof Integer) {
// Checking permissions by mapId ...
final int mapId = (Integer) targetDomainObject;
result = hasPrivilege(mapId, perm);
} else if (targetDomainObject instanceof Mindmap) {
final Mindmap map = (Mindmap) targetDomainObject;
result = hasPrivilege(map, perm);
} else if (targetDomainObject instanceof Collaborator collab) {
// Read only operations checks ...
assert user != null;
result = user.identityEquality(collab) || readAdvice.getMindmapService().isAdmin(user);
} else {
throw new IllegalArgumentException("Unsupported check control of permissions");
}
if (!result) {
logger.debug("User '" + (user != null ? user.getEmail() : "none") + "' not allowed to invoke");
}
return result;
}
@Override
public boolean hasPermission(
@NotNull Authentication auth, Serializable targetId, @NotNull String targetType, @NotNull Object
permission) {
logger.log(Level.FATAL, "Unsupported privilege: auth: " + auth + ",targetId:" + targetType + ",targetType:" + targetType + ", permission:" + permission);
return false;
}
private boolean hasPrivilege(@NotNull int mapId, @NotNull MapAccessPermission permission) {
boolean result;
final User user = Utils.getUser();
if (MapAccessPermission.READ == permission) {
result = readAdvice.isAllowed(user, mapId);
} else {
result = updateAdvice.isAllowed(user, mapId);
}
return result;
}
private boolean hasPrivilege(@NotNull Mindmap map, @NotNull MapAccessPermission permission) {
boolean result;
final User user = Utils.getUser();
if (MapAccessPermission.READ == permission) {
result = readAdvice.isAllowed(user, map);
} else {
result = updateAdvice.isAllowed(user, map);
}
return result;
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright [2022] [wisemapping]
*
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
* It is basically the Apache License, Version 2.0 (the "License") plus the
* "powered by wisemapping" text requirement on every single page;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the license at
*
* http://www.wisemapping.org/license
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wisemapping.security;
import com.wisemapping.model.Mindmap;
import com.wisemapping.model.User;
import com.wisemapping.service.MindmapService;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
public abstract class MapPermissionsSecurityAdvice {
@Autowired private MindmapService mindmapService;
protected abstract boolean isAllowed(@Nullable User user, Mindmap map);
protected abstract boolean isAllowed(@Nullable User user, int mapId);
protected MindmapService getMindmapService() {
return mindmapService;
}
}

View File

@ -16,32 +16,22 @@
* limitations under the License.
*/
package com.wisemapping.security.aop;
package com.wisemapping.security;
import com.wisemapping.model.CollaborationRole;
import com.wisemapping.model.Mindmap;
import com.wisemapping.model.User;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.stereotype.Component;
public class ViewBaseSecurityAdvise
extends BaseSecurityAdvice
implements MethodInterceptor {
@Component
public class ReadSecurityAdvise
extends MapPermissionsSecurityAdvice {
@Override
public Object invoke(@NotNull MethodInvocation methodInvocation) throws Throwable {
checkRole(methodInvocation);
return methodInvocation.proceed();
}
@Override
protected boolean isAllowed(@Nullable User user, Mindmap map) {
return getMindmapService().hasPermissions(user, map, CollaborationRole.VIEWER);
}
@Override
protected boolean isAllowed(@Nullable User user, int mapId) {
return getMindmapService().hasPermissions(user, mapId, CollaborationRole.VIEWER);
}

View File

@ -0,0 +1,48 @@
/*
* Copyright [2022] [wisemapping]
*
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
* It is basically the Apache License, Version 2.0 (the "License") plus the
* "powered by wisemapping" text requirement on every single page;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the license at
*
* http://www.wisemapping.org/license
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wisemapping.security;
import com.wisemapping.model.CollaborationRole;
import com.wisemapping.model.Mindmap;
import com.wisemapping.model.User;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.stereotype.Component;
@Component
public class UpdateSecurityAdvise
extends MapPermissionsSecurityAdvice {
@Override
protected boolean isAllowed(@Nullable User user, @NotNull Mindmap map) {
boolean result;
if (map.getCreator() == null) {
// This means that the map is new and is an add operation.
result = true;
} else {
result = getMindmapService().hasPermissions(user, map, CollaborationRole.EDITOR);
}
return result;
}
@Override
protected boolean isAllowed(@Nullable User user, int mapId) {
return getMindmapService().hasPermissions(user, mapId, CollaborationRole.EDITOR);
}
}

View File

@ -1,65 +0,0 @@
/*
* Copyright [2022] [wisemapping]
*
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
* It is basically the Apache License, Version 2.0 (the "License") plus the
* "powered by wisemapping" text requirement on every single page;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the license at
*
* http://www.wisemapping.org/license
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wisemapping.security.aop;
import com.wisemapping.exceptions.AccessDeniedSecurityException;
import com.wisemapping.model.Collaborator;
import com.wisemapping.model.Mindmap;
import com.wisemapping.model.User;
import com.wisemapping.security.Utils;
import com.wisemapping.service.MindmapService;
import org.aopalliance.intercept.MethodInvocation;
import org.jetbrains.annotations.Nullable;
public abstract class BaseSecurityAdvice {
private MindmapService mindmapService = null;
public void checkRole(MethodInvocation methodInvocation) throws AccessDeniedSecurityException {
final User user = Utils.getUser();
final Object argument = methodInvocation.getArguments()[0];
boolean isAllowed;
if (argument instanceof Mindmap) {
isAllowed = isAllowed(user, (Mindmap) argument) || mindmapService.isAdmin(user);
} else if (argument instanceof Integer) {
isAllowed = isAllowed(user, ((Integer) argument)) || mindmapService.isAdmin(user);
} else if (argument instanceof Collaborator) {
// Read operation find on the user are allowed ...
isAllowed = user.identityEquality((Collaborator) argument) || mindmapService.isAdmin(user);
} else {
throw new IllegalArgumentException("Argument " + argument);
}
if (!isAllowed) {
throw new AccessDeniedSecurityException("User '" + (user != null ? user.getEmail() : "none") + "' not allowed to invoke:" + methodInvocation);
}
}
protected abstract boolean isAllowed(@Nullable User user, Mindmap map);
protected abstract boolean isAllowed(@Nullable User user, int mapId);
protected MindmapService getMindmapService() {
return mindmapService;
}
public void setMindmapService(MindmapService service) {
this.mindmapService = service;
}
}

View File

@ -1,55 +0,0 @@
/*
* Copyright [2022] [wisemapping]
*
* Licensed under WiseMapping Public License, Version 1.0 (the "License").
* It is basically the Apache License, Version 2.0 (the "License") plus the
* "powered by wisemapping" text requirement on every single page;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the license at
*
* http://www.wisemapping.org/license
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wisemapping.security.aop;
import com.wisemapping.model.CollaborationRole;
import com.wisemapping.model.Mindmap;
import com.wisemapping.model.User;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class UpdateSecurityAdvise
extends BaseSecurityAdvice
implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
checkRole(methodInvocation);
return methodInvocation.proceed();
}
@Override
protected boolean isAllowed(@Nullable User user, @NotNull Mindmap map) {
boolean result;
if (map.getCreator() == null) {
// This means that the map is new and is an add operation.
result = true;
} else {
result = getMindmapService().hasPermissions(user, map, CollaborationRole.EDITOR);
}
return result;
}
@Override
protected boolean isAllowed(@Nullable User user, int mapId) {
return getMindmapService().hasPermissions(user, mapId, CollaborationRole.EDITOR);
}
}

View File

@ -27,8 +27,8 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.access.prepost.PreAuthorize;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.List;
@ -92,37 +92,42 @@ public class MindmapServiceImpl
}
@Override
@PreAuthorize("hasPermission(#user, 'READ')")
public Mindmap getMindmapByTitle(String title, User user) {
return mindmapManager.getMindmapByTitle(title, user);
}
@Override
@Nullable
@PreAuthorize("hasPermission(#id, 'READ')")
public Mindmap findMindmapById(int id) {
return mindmapManager.getMindmapById(id);
}
@NotNull
@Override
@PreAuthorize("hasAnyRole('USER', 'ADMIN') && hasPermission(#user, 'READ')")
public List<Mindmap> findMindmapsByUser(@NotNull User user) {
return mindmapManager.findMindmapByUser(user);
}
@Override
@PreAuthorize("hasAnyRole('USER', 'ADMIN') && hasPermission(#user, 'READ')")
public List<Collaboration> findCollaborations(@NotNull User user) {
return mindmapManager.findCollaboration(user.getId());
}
@Override
public void updateMindmap(@NotNull Mindmap mindMap, boolean saveHistory) throws WiseMappingException {
if (mindMap.getTitle() == null || mindMap.getTitle().length() == 0) {
@PreAuthorize("hasAnyRole('USER', 'ADMIN') && hasPermission(#mindmap, 'WRITE')")
public void updateMindmap(@NotNull Mindmap mindmap, boolean saveHistory) throws WiseMappingException {
if (mindmap.getTitle() == null || mindmap.getTitle().length() == 0) {
throw new WiseMappingException("The title can not be empty");
}
// Check that what we received a valid mindmap...
final String xml;
try {
xml = mindMap.getXmlStr().trim();
xml = mindmap.getXmlStr().trim();
} catch (UnsupportedEncodingException e) {
throw new WiseMappingException("Could not be decoded.", e);
}
@ -131,10 +136,11 @@ public class MindmapServiceImpl
throw new WiseMappingException("Map seems not to be a valid mindmap: '" + xml + "'");
}
mindmapManager.updateMindmap(mindMap, saveHistory);
mindmapManager.updateMindmap(mindmap, saveHistory);
}
@Override
@PreAuthorize("hasAnyRole('USER', 'ADMIN') && hasPermission(#mindmap, 'WRITE')")
public void removeCollaboration(@NotNull Mindmap mindmap, @NotNull Collaboration collaboration) throws CollaborationException {
// remove collaborator association
final Mindmap mindMap = collaboration.getMindMap();
@ -149,6 +155,7 @@ public class MindmapServiceImpl
}
@Override
@PreAuthorize("hasAnyRole('USER', 'ADMIN') && hasPermission(#mindmap, 'READ')")
public void removeMindmap(@NotNull Mindmap mindmap, @NotNull User user) throws WiseMappingException {
if (mindmap.getCreator().identityEquality(user)) {
mindmapManager.removeMindmap(mindmap);
@ -161,9 +168,10 @@ public class MindmapServiceImpl
}
@Override
public void addMindmap(@NotNull Mindmap map, @NotNull User user) {
@PreAuthorize("hasAnyRole('USER', 'ADMIN') && hasPermission(#mindmap, 'WRITE')")
public void addMindmap(@NotNull Mindmap mindmap, @NotNull User user) {
final String title = map.getTitle();
final String title = mindmap.getTitle();
if (title == null || title.length() == 0) {
throw new IllegalArgumentException("The tile can not be empty");
@ -175,20 +183,21 @@ public class MindmapServiceImpl
}
final Calendar creationTime = Calendar.getInstance();
map.setLastEditor(user);
map.setCreationTime(creationTime);
map.setLastModificationTime(creationTime);
map.setCreator(user);
mindmap.setLastEditor(user);
mindmap.setCreationTime(creationTime);
mindmap.setLastModificationTime(creationTime);
mindmap.setCreator(user);
// Add map creator with owner permissions ...
final User dbUser = userService.getUserBy(user.getId());
final Collaboration collaboration = new Collaboration(CollaborationRole.OWNER, dbUser, map);
map.getCollaborations().add(collaboration);
final Collaboration collaboration = new Collaboration(CollaborationRole.OWNER, dbUser, mindmap);
mindmap.getCollaborations().add(collaboration);
mindmapManager.addMindmap(dbUser, map);
mindmapManager.addMindmap(dbUser, mindmap);
}
@Override
@PreAuthorize("hasAnyRole('USER', 'ADMIN') && hasPermission(#mindmap, 'WRITE')")
public void addCollaboration(@NotNull Mindmap mindmap, @NotNull String email, @NotNull CollaborationRole role, @Nullable String message)
throws CollaborationException {
@ -222,7 +231,8 @@ public class MindmapServiceImpl
}
}
private Collaborator addCollaborator(String email) {
private Collaborator addCollaborator(@NotNull String email) {
// Add a new collaborator ...
Collaborator collaborator = mindmapManager.findCollaborator(email);
if (collaborator == null) {
@ -236,11 +246,13 @@ public class MindmapServiceImpl
@Override
@PreAuthorize("hasAnyRole('USER', 'ADMIN') && hasPermission(#mindmap, 'READ')")
public List<MindMapHistory> findMindmapHistory(int mindmapId) {
return mindmapManager.getHistoryFrom(mindmapId);
}
@Override
@PreAuthorize("hasAnyRole('USER', 'ADMIN') && hasPermission(#mindmap, 'WRITE')")
public void revertChange(@NotNull Mindmap mindmap, int historyId)
throws WiseMappingException {
final MindMapHistory history = mindmapManager.getHistory(historyId);
@ -249,6 +261,7 @@ public class MindmapServiceImpl
}
@Override
@PreAuthorize("hasAnyRole('USER', 'ADMIN') && hasPermission(#mindmap, 'READ')")
public MindMapHistory findMindmapHistory(int id, int hid) throws WiseMappingException {
final List<MindMapHistory> mindmapHistory = this.findMindmapHistory(id);
MindMapHistory result = null;
@ -266,6 +279,7 @@ public class MindmapServiceImpl
}
@Override
@PreAuthorize("hasAnyRole('USER', 'ADMIN') && hasPermission(#collaborator, 'WRITE')")
public void updateCollaboration(@NotNull Collaborator collaborator, @NotNull Collaboration collaboration) throws WiseMappingException {
if (!collaborator.identityEquality(collaboration.getCollaborator())) {
throw new WiseMappingException("No enough permissions for this operation.");
@ -279,6 +293,7 @@ public class MindmapServiceImpl
return this.lockManager;
}
@PreAuthorize("hasAnyRole('USER', 'ADMIN') && hasPermission(#mindmap, 'READ')")
private Collaboration getCollaborationBy(@NotNull final String email, @NotNull final Set<Collaboration> collaborations) {
Collaboration collaboration = null;

View File

@ -24,7 +24,6 @@
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/wisemapping-aop.xml
/WEB-INF/wisemapping-datasource.xml
/WEB-INF/wisemapping-dao.xml
/WEB-INF/wisemapping-service.xml

View File

@ -1,45 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="viewSecurityAdvisor"
class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice" ref="viewSecurityAdvice"/>
<property name="mappedNames">
<list>
<value>getMindmapUserBy</value>
<value>getMindmapById</value>
<value>linkLabel</value>
<value>find*</value>
<value>filter*</value>
<!-- Remove can be performed in view only maps -->
<value>removeMindmap</value>
</list>
</property>
</bean>
<bean id="updateSecurityAdvisor"
class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="advice" ref="updateSecurityAdvice"/>
<property name="mappedNames">
<list>
<value>save*</value>
<value>update*</value>
<value>add*</value>
<value>revert*</value>
<value>removeCollaboration</value>
</list>
</property>
</bean>
<bean id="viewSecurityAdvice" class="com.wisemapping.security.aop.ViewBaseSecurityAdvise">
<property name="mindmapService" ref="mindmapService"/>
</bean>
<bean id="updateSecurityAdvice" class="com.wisemapping.security.aop.UpdateSecurityAdvise">
<property name="mindmapService" ref="mindmapService"/>
</bean>
</beans>

View File

@ -65,8 +65,6 @@
<property name="interceptorNames">
<list>
<value>txInterceptor</value>
<value>viewSecurityAdvisor</value>
<value>updateSecurityAdvisor</value>
</list>
</property>
<property name="target" ref="mindMapServiceTarget"/>