From de23f72bf11a46ab0dd3b5f67d82d4101b20b897 Mon Sep 17 00:00:00 2001 From: Martin Stockhammer Date: Tue, 24 Nov 2020 21:28:42 +0100 Subject: [PATCH] Refactoring of role API and new Role V2 REST service --- README.adoc | 11 + .../common/ldap/role/LdapRoleMapper.java | 1 - .../archiva/redback/rest/api/Constants.java | 2 +- .../archiva/redback/rest/api/MessageKeys.java | 13 +- .../rest/api/model/v2/Application.java | 89 +++ .../rest/api/model/v2/BaseGroupInfo.java | 80 +++ .../rest/api/model/v2/BaseRoleInfo.java | 262 +++++++++ .../rest/api/model/v2/BaseUserInfo.java | 67 +++ .../redback/rest/api/model/v2/Role.java | 104 ++++ .../redback/rest/api/model/v2/RoleInfo.java | 182 +++++++ .../redback/rest/api/model/v2/RoleTree.java | 74 +++ .../redback/rest/api/model/v2/UserInfo.java | 36 +- .../rest/api/services/v2/RoleService.java | 456 +++++++++------- .../rest/api/services/v2/UserService.java | 61 ++- .../DefaultRoleManagementService.java | 5 +- .../rest/services/v2/BaseRedbackService.java | 145 +++++ .../rest/services/v2/DefaultGroupService.java | 8 +- .../rest/services/v2/DefaultRoleService.java | 434 +++++++++++++++ .../rest/services/v2/DefaultUserService.java | 185 ++++--- .../redback/rest/services/v2/QueryHelper.java | 168 ++++++ .../resources/META-INF/spring-context.xml | 2 +- .../v2/AbstractNativeRestServices.java | 2 +- .../services/v2/NativeRoleServiceTest.java | 505 ++++++++++++++++++ .../services/v2/NativeUserServiceTest.java | 132 ++++- .../redback/rbac/AbstractRBACManager.java | 83 ++- .../archiva/redback/rbac/AbstractRole.java | 9 +- .../archiva/redback/rbac/RBACManager.java | 26 +- .../org/apache/archiva/redback/rbac/Role.java | 26 + .../rbac/cached/CachedRbacManager.java | 22 +- .../redback/rbac/jpa/JpaRbacManager.java | 18 +- .../redback/rbac/jpa/model/JpaRole.java | 44 +- .../redback/rbac/jpa/model/RoleId.java | 59 ++ .../redback/rbac/ldap/LdapRbacManager.java | 45 +- .../redback/rbac/memory/MemoryRole.java | 22 + .../redback/role/DefaultRoleManager.java | 23 +- .../redback/role/RoleExistsException.java | 33 ++ .../archiva/redback/role/RoleManager.java | 4 +- .../redback/role/RoleNotFoundException.java | 33 ++ .../processor/DefaultRoleModelProcessor.java | 3 +- .../DefaultRoleTemplateProcessor.java | 40 +- .../role/template/RoleTemplateProcessor.java | 26 +- ...bstractRbacManagerPerformanceTestCase.java | 2 + .../tests/AbstractRbacManagerTestCase.java | 6 +- .../redback/tests/utils/RBACDefaults.java | 3 + 44 files changed, 3173 insertions(+), 378 deletions(-) create mode 100644 redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/Application.java create mode 100644 redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseGroupInfo.java create mode 100644 redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseRoleInfo.java create mode 100644 redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseUserInfo.java create mode 100644 redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/Role.java create mode 100644 redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/RoleInfo.java create mode 100644 redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/RoleTree.java create mode 100644 redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/BaseRedbackService.java create mode 100644 redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultRoleService.java create mode 100644 redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/QueryHelper.java create mode 100644 redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeRoleServiceTest.java create mode 100644 redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/RoleId.java create mode 100644 redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleExistsException.java create mode 100644 redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleNotFoundException.java diff --git a/README.adoc b/README.adoc index 568868b6..646c17ae 100644 --- a/README.adoc +++ b/README.adoc @@ -2,6 +2,17 @@ Archiva Redback - Documentation =============================== :toc: +== Update Information for 3.0 + +=== Database Schema Changes + +==== org.apache.archiva.redback.rbac.Role + +New fields: +id, modelId, isTemplateInstance, resource + +Primary key changed from name to combined key id,name + == How to build and publish the pages for the archiva web content diff --git a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/LdapRoleMapper.java b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/LdapRoleMapper.java index 1ee2cfe5..43bea954 100644 --- a/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/LdapRoleMapper.java +++ b/redback-common/redback-common-ldap/src/main/java/org/apache/archiva/redback/common/ldap/role/LdapRoleMapper.java @@ -66,7 +66,6 @@ public interface LdapRoleMapper boolean hasRole( DirContext context, String role ) throws MappingException; - /** * @return the base dn which contains all ldap groups */ diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Constants.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Constants.java index 3faaf8ca..f70ad584 100644 --- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Constants.java +++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/Constants.java @@ -23,7 +23,7 @@ package org.apache.archiva.redback.rest.api; */ public interface Constants { - String DEFAULT_PAGE_LIMIT = "1000"; + String DEFAULT_PAGE_LIMIT = "100"; } diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/MessageKeys.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/MessageKeys.java index 6f7219ad..51f762d0 100644 --- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/MessageKeys.java +++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/MessageKeys.java @@ -40,10 +40,19 @@ public interface MessageKeys String ERR_USER_ADMIN_EXISTS = "rb.user.admin.exists"; String ERR_USER_ADMIN_BAD_NAME = "rb.user.admin.badname"; String ERR_USER_NOT_FOUND = "rb.user.not_found"; + String ERR_USER_BAD_PASSWORD = "rb.user.bad.password"; String ERR_PASSWORD_VIOLATION = "rb.user.password_violation"; + String ERR_LDAP_GENERIC = "rb.ldap.error"; String ERR_ROLE_MAPPING = "rb.role.mapping.error"; String ERR_ROLE_MAPPING_NOT_FOUND = "rb.role.mapping.not_found"; + String ERR_ROLE_NOT_FOUND = "rb.role.not_found"; + // A template instance not found. With arguments templateId, resource + String ERR_ROLE_INSTANCE_NOT_FOUND = "rb.role.instance.not_found"; + String ERR_ROLE_EXISTS = "rb.role.exists"; + // A template instance exists. With arguments templateId, resource + String ERR_ROLE_INSTANCE_EXISTS = "rb.role.instance.exists"; + String ERR_AUTH_BAD_CODE = "rb.auth.bad_authorization_code"; String ERR_AUTH_INVALID_CREDENTIALS = "rb.auth.invalid_credentials"; String ERR_AUTH_FAIL_MSG = "rb.auth.fail"; @@ -52,9 +61,11 @@ public interface MessageKeys String ERR_AUTH_UNSUPPORTED_GRANT_TYPE = "rb.auth.unsupported_grant"; String ERR_AUTH_INVALID_TOKEN = "rb.auth.invalid_token"; String ERR_AUTH_UNAUTHORIZED_REQUEST = "rb.auth.unauthorized_request"; + String ERR_PASSWD_RESET_FAILED = "rb.passwd.reset.fail"; - String ERR_USER_BAD_PASSWORD = "rb.user.bad.password"; + String ERR_REGISTRATION_KEY_INVALID = "rb.registration.key.invalid"; String ERR_REGISTRATION_USER_VALIDATED = "rb.registration.user.validated"; String ERR_REGISTRATION_ROLE_ASSIGNMENT_FAILED = "rb.registration.role.assignment.failed"; + } diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/Application.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/Application.java new file mode 100644 index 00000000..c38392cb --- /dev/null +++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/Application.java @@ -0,0 +1,89 @@ +package org.apache.archiva.redback.rest.api.model.v2; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.xml.bind.annotation.XmlRootElement; +import java.io.Serializable; + +/** + * @author Olivier Lamy + */ +@XmlRootElement( name = "application" ) +@Schema(name="Application", description = "A single application that is used for defining roles") +public class Application + implements Serializable +{ + private static final long serialVersionUID = -4738856943947960583L; + + private String version; + private String id; + private String description; + private String longDescription; + + public Application() + { + // no op + } + + @Schema(description = "The application version. Used to separate different sets of roles.") + public String getVersion() + { + return version; + } + + public void setVersion( String version ) + { + this.version = version; + } + + @Schema(description = "The identifier of the application") + public String getId() + { + return id; + } + + public void setId( String id ) + { + this.id = id; + } + + @Schema(description = "A short description.") + public String getDescription() + { + return description; + } + + public void setDescription( String description ) + { + this.description = description; + } + + @Schema(description = "May be a longer explanation, of the application purpose and its defined roles.") + public String getLongDescription() + { + return longDescription; + } + + public void setLongDescription( String longDescription ) + { + this.longDescription = longDescription; + } +} diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseGroupInfo.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseGroupInfo.java new file mode 100644 index 00000000..22ff8307 --- /dev/null +++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseGroupInfo.java @@ -0,0 +1,80 @@ +package org.apache.archiva.redback.rest.api.model.v2;/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + */ + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.io.Serializable; + +/** + * Information about a group. + * + * @since 3.0 + * @author Martin Stockhammer + */ +@Schema( name = "Group", description = "Group information" ) +public class BaseGroupInfo implements Serializable +{ + private static final long serialVersionUID = 2945927911204165322L; + private String id; + private String groupName; + private String description = ""; + + public BaseGroupInfo( ) + { + + } + + public BaseGroupInfo( String id, String groupName ) + { + this.id = id; + this.groupName = groupName; + } + + @Schema(description = "The name of the group") + public String getGroupName( ) + { + return groupName; + } + + public void setGroupName( String groupName ) + { + this.groupName = groupName; + } + + @Schema( description = "The unique identifier of the group" ) + public String getId( ) + { + return id; + } + + public void setId( String id ) + { + this.id = id; + } + + @Schema( description = "A description of the group" ) + public String getDescription( ) + { + return description; + } + + public void setDescription( String description ) + { + this.description = description; + } +} diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseRoleInfo.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseRoleInfo.java new file mode 100644 index 00000000..126a5e03 --- /dev/null +++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseRoleInfo.java @@ -0,0 +1,262 @@ +package org.apache.archiva.redback.rest.api.model.v2;/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + */ + +import io.swagger.v3.oas.annotations.media.Schema; +import org.apache.archiva.redback.rbac.Role; +import org.apache.archiva.redback.role.model.ModelRole; + +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * Basic role information. This class contains only the standard attributes used for displaying a role. + * + * @author Martin Stockhammer + */ +@Schema(name="BaseRoleInfo", description = "Basic role attributes") +public class BaseRoleInfo implements Serializable +{ + private static final long serialVersionUID = -6725489773301720068L; + + protected String id; + protected String name; + protected String description = ""; + protected boolean permanent = false; + protected String modelId = ""; + protected String resource = ""; + protected boolean isTemplateInstance = false; + protected boolean assignable = true; + private String applicationId = ""; + private boolean isChild = false; + + protected boolean assigned = false; + private List children = new ArrayList<>( 0 ); + + public BaseRoleInfo() { + + } + + public static BaseRoleInfo ofName(String name) { + BaseRoleInfo info = new BaseRoleInfo( ); + info.setName( name ); + return info; + } + + public static BaseRoleInfo ofId(String id) { + BaseRoleInfo info = new BaseRoleInfo( ); + info.setId( id ); + return info; + } + + public static BaseRoleInfo of(Role rbacRole) { + return of( rbacRole, new BaseRoleInfo( ) ); + } + + + public static T of( Role rbacRole, T role ) { + role.id = rbacRole.getId( ); + role.name = rbacRole.getName( ); + role.description = rbacRole.getDescription( ) == null ?"": rbacRole.getDescription(); + role.permanent = rbacRole.isPermanent( ); + role.modelId = rbacRole.getModelId( ); + role.resource = rbacRole.getResource( ); + role.isTemplateInstance = rbacRole.isTemplateInstance( ); + role.assignable = rbacRole.isAssignable( ); + return role; + } + + + + @Schema(description = "The role name") + public String getName() + { + return name; + } + + public void setName( String name ) + { + this.name = name; + } + + @Schema( description = "A description of the role" ) + public String getDescription( ) + { + return description; + } + + public void setDescription( String description ) + { + this.description = description; + } + + @Schema( description = "True, if this role cannot be deleted.") + public boolean isPermanent() + { + return permanent; + } + + public void setPermanent( boolean permanent ) + { + this.permanent = permanent; + } + + @Schema(description = "The identifier of this role") + public String getId( ) + { + return id; + } + + public void setId( String id ) + { + this.id = id; + } + + @Schema(description = "The model this role is derived from") + public String getModelId( ) + { + return modelId; + } + + public void setModelId( String modelId ) + { + this.modelId = modelId; + } + + @Schema(description = "The resource this model is built for, if it is built by a template.") + public String getResource( ) + { + return resource; + } + + public void setResource( String resource ) + { + this.resource = resource; + } + + @Schema(description = "True, if this is a instance of a role template") + public boolean isTemplateInstance( ) + { + return isTemplateInstance; + } + + public void setTemplateInstance( boolean templateInstance ) + { + isTemplateInstance = templateInstance; + } + + @Schema(description = "Roles that are children of this role. This field may not be populated, depending on the REST method call.") + public List getChildren( ) + { + return children; + } + + public void setChildren( List children ) + { + this.children = children; + } + + public void addChild(BaseRoleInfo child) { + if (!this.children.contains( child )) + { + this.children.add( child ); + } + } + + @Schema(description = "This attribute is only set at specific REST calls, that return roles, that are either assigned or not assigned to a given user.") + public boolean isAssigned( ) + { + return assigned; + } + + public void setAssigned( boolean assigned ) + { + this.assigned = assigned; + } + + + @Override + public boolean equals( Object o ) + { + if ( this == o ) return true; + if ( o == null || getClass( ) != o.getClass( ) ) return false; + + BaseRoleInfo that = (BaseRoleInfo) o; + + return id.equals( that.id ); + } + + @Schema( description = "If true, the role is assignable to users or roles. Otherwise, it can be used only as parent role.") + public boolean isAssignable() + { + return assignable; + } + + public void setAssignable( boolean assignable ) + { + this.assignable = assignable; + } + + @Override + public int hashCode( ) + { + return id.hashCode( ); + } + + @Override + public String toString( ) + { + final StringBuilder sb = new StringBuilder( "BaseRoleInfo{" ); + sb.append( "id='" ).append( id ).append( '\'' ); + sb.append( ", name='" ).append( name ).append( '\'' ); + sb.append( ", description='" ).append( description ).append( '\'' ); + sb.append( ", permanent=" ).append( permanent ); + sb.append( ", modelId='" ).append( modelId ).append( '\'' ); + sb.append( ", resource='" ).append( resource ).append( '\'' ); + sb.append( ", isTemplateInstance=" ).append( isTemplateInstance ); + sb.append( '}' ); + return sb.toString( ); + } + + @Schema(description = "Application id, where this role belongs to. This is only filled by certain REST methods.") + public String getApplicationId( ) + { + return applicationId; + } + + public void setApplicationId( String applicationId ) + { + this.applicationId = applicationId; + } + + public boolean isChild( ) + { + return isChild; + } + + public void setChild( boolean child ) + { + isChild = child; + } + + @XmlTransient + public boolean isNotChild() { + return !isChild; + } +} diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseUserInfo.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseUserInfo.java new file mode 100644 index 00000000..7903a1ca --- /dev/null +++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/BaseUserInfo.java @@ -0,0 +1,67 @@ +package org.apache.archiva.redback.rest.api.model.v2;/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + */ + +import io.swagger.v3.oas.annotations.media.Schema; + +import javax.xml.bind.annotation.XmlElement; +import java.io.Serializable; + +/** + * @author Martin Stockhammer + */ +@Schema( name = "BaseUserInfo", description = "Basic user information" ) +public class BaseUserInfo implements Serializable +{ + private static final long serialVersionUID = 4643187400578104895L; + protected String userId; + private String id; + + + public BaseUserInfo( ) + { + } + + public BaseUserInfo( String id , String userId ) + { + this.userId = userId; + this.id = id; + } + + @Schema( name = "user_id", description = "The user id" ) + @XmlElement( name = "user_id" ) + public String getUserId( ) + { + return userId; + } + + public void setUserId( String userId ) + { + this.userId = userId; + } + + @Schema( description = "User id that is unique over all user managers" ) + public String getId( ) + { + return id; + } + + public void setId( String id ) + { + this.id = id; + } +} diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/Role.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/Role.java new file mode 100644 index 00000000..f5909a9c --- /dev/null +++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/Role.java @@ -0,0 +1,104 @@ +package org.apache.archiva.redback.rest.api.model.v2;/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + */ + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * This class is used for role update. Contains only the role attributes, that can be updated. + * + * @author Martin Stockhammer + */ +@Schema(name="Role",description="Role attributes that are used for updating a role") +public class Role implements Serializable +{ + private static final long serialVersionUID = 3238571295658509062L; + + protected String name; + protected String id; + protected String description; + protected boolean permanent = false; + /** + * The ids of all the assigned users. + */ + protected List assignedUsers = new ArrayList<>( 0 ); + + @Schema(description = "The role name") + public String getName() + { + return name; + } + + public void setName( String name ) + { + this.name = name; + } + + @Schema( description = "A description of the role" ) + public String getDescription( ) + { + return description; + } + + public void setDescription( String description ) + { + this.description = description; + } + + @Schema( description = "True, if this role cannot be deleted.") + public boolean isPermanent() + { + return permanent; + } + + public void setPermanent( boolean permanent ) + { + this.permanent = permanent; + } + + @Schema(description = "The identifier of this role") + public String getId( ) + { + return id; + } + + public void setId( String id ) + { + this.id = id; + } + + @Schema( description = "List of user ids that are assigned to this role.") + public List getAssignedUsers( ) + { + return assignedUsers; + } + + public void setAssignedUsers( List assignedUsers ) + { + this.assignedUsers = assignedUsers; + } + + public void addAssignedUser( BaseUserInfo id) { + this.assignedUsers.add( id ); + } + + +} diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/RoleInfo.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/RoleInfo.java new file mode 100644 index 00000000..aa77836c --- /dev/null +++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/RoleInfo.java @@ -0,0 +1,182 @@ +package org.apache.archiva.redback.rest.api.model.v2; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + + +import io.swagger.v3.oas.annotations.media.Schema; +import org.apache.archiva.redback.rbac.Role; + +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlTransient; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Result object for role information. + * + * @author Martin Stockhammer + * @since 3.0 + */ +@XmlRootElement( name = "role" ) +@Schema(name="RoleInfo",description = "Information about role") +public class RoleInfo extends BaseRoleInfo + implements Serializable +{ + private static final long serialVersionUID = -3506615158923923845L; + + /** + * Field childRoleNames + */ + private List childRoleIds = new ArrayList<>(0); + + /** + * Field permissions + */ + private List permissions = new ArrayList<>(0); + + /** + * The names of all parent roles + */ + private List parentRoleIds = new ArrayList<>(0); + + /** + * The ids of all the assigned users. + */ + protected List assignedUsers = new ArrayList<>( 0 ); + + @Schema( description = "List of user ids that are assigned to this role.") + public List getAssignedUsers( ) + { + return assignedUsers; + } + + public void setAssignedUsers( List assignedUsers ) + { + this.assignedUsers = assignedUsers; + } + + public void addAssignedUser( BaseUserInfo id) { + this.assignedUsers.add( id ); + } + + public RoleInfo() + { + // no op + } + + + public static RoleInfo of( Role rbacRole) { + RoleInfo role = BaseRoleInfo.of( rbacRole, new RoleInfo( ) ); + return role; + } + + @XmlTransient + @Override + public List getChildren( ) + { + return super.getChildren( ); + } + + @Schema( description = "List of names of children roles") + public List getChildRoleIds() + { + return childRoleIds; + } + + public void setChildRoleIds( List childRoleIds ) + { + this.childRoleIds = childRoleIds; + } + + @Schema( description = "List of permissions assigned to this role.") + public List getPermissions() + { + return permissions; + } + + public void setPermissions( List permissions ) + { + this.permissions = permissions; + } + + @Schema(description = "List of names of roles that are parents of this role.") + public List getParentRoleIds() + { + return parentRoleIds; + } + + public void setParentRoleIds( List parentRoleIds ) + { + this.parentRoleIds = parentRoleIds; + } + + @Override + public boolean isChild( ) + { + return getParentRoleIds( ).size( ) > 0; + } + + @Override + public int hashCode() + { + return getName( ) != null ? getName( ).hashCode() : 0; + } + + @Override + public String toString( ) + { + final StringBuilder sb = new StringBuilder( "RoleInfo{" ); + sb.append( "name='" ).append( getName( ) ).append( '\'' ); + sb.append( ", id='" ).append( getId( ) ).append( '\'' ); + sb.append( ", description='" ).append( getDescription( ) ).append( '\'' ); + sb.append( ", assignable=" ).append( assignable ); + sb.append( ", childRoleNames=" ).append( childRoleIds ); + sb.append( ", permissions=" ).append( permissions ); + sb.append( ", parentRoleNames=" ).append( parentRoleIds ); + sb.append( ", assignedUsers=" ).append( assignedUsers ); + sb.append( ", permanent=" ).append( isPermanent( ) ); + sb.append( ", modelId='" ).append( modelId ).append( '\'' ); + sb.append( ", resource='" ).append( resource ).append( '\'' ); + sb.append( ", isTemplateInstance=" ).append( isTemplateInstance ); + sb.append( '}' ); + return sb.toString( ); + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) + { + return true; + } + if ( o == null || getClass() != o.getClass() ) + { + return false; + } + + RoleInfo role = (RoleInfo) o; + + return Objects.equals( getName( ), role.getName( ) ); + + } + + +} diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/RoleTree.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/RoleTree.java new file mode 100644 index 00000000..1af65fd5 --- /dev/null +++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/RoleTree.java @@ -0,0 +1,74 @@ +package org.apache.archiva.redback.rest.api.model.v2;/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + */ + +import io.swagger.v3.oas.annotations.media.Schema; + +import java.io.Serializable; +import java.util.List; +import java.util.Map; + +/** + * Information about roles of a application. + * + * @author Martin Stockhammer + */ +@Schema(name="RoleTree",description = "Tree of roles defined. The root roles have no parent. Each role may have children recursively.") +public class RoleTree implements Serializable +{ + private static final long serialVersionUID = 6893397477073625729L; + + String userId; + Map applications; + List rootRoles; + + @Schema(description = "The user id for which the assigned flags are set on the roles.") + public String getUserId( ) + { + return userId; + } + + public void setUserId( String userId ) + { + this.userId = userId; + } + + @Schema(description = "Information about the applications that define roles. The keys of the map are the application ids.") + public Map getApplications( ) + { + return applications; + } + + public void setApplications( Map applications ) + { + this.applications = applications; + } + + + @Schema(description = "The list of roles directly assigned to this application. Roles may contain children roles.") + public List getRootRoles( ) + { + return rootRoles; + } + + public void setRootRoles( List rootRoles ) + { + this.rootRoles = rootRoles; + } + + +} diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/UserInfo.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/UserInfo.java index 274848cb..c6043d38 100644 --- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/UserInfo.java +++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/model/v2/UserInfo.java @@ -2,13 +2,11 @@ package org.apache.archiva.redback.rest.api.model.v2; import io.swagger.v3.oas.annotations.media.Schema; -import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement; import java.io.Serializable; import java.time.Instant; import java.time.OffsetDateTime; import java.time.ZoneId; -import java.util.List; /* * Licensed to the Apache Software Foundation (ASF) under one @@ -31,14 +29,11 @@ import java.util.List; @XmlRootElement( name = "user" ) @Schema(name="User", description = "User information data") -public class UserInfo +public class UserInfo extends BaseUserInfo implements Serializable { private static final long serialVersionUID = 822423853981984867L; - private String id; - - private String userId; private String fullName; @@ -125,18 +120,6 @@ public class UserInfo } - @Schema( name = "user_id", description = "The user id" ) - @XmlElement( name = "user_id" ) - public String getUserId( ) - { - return userId; - } - - public void setUserId( String userId ) - { - this.userId = userId; - } - @Schema( description = "The full name of the user" ) public String getFullName( ) { @@ -282,22 +265,11 @@ public class UserInfo this.validationToken = validationToken; } - @Schema( description = "User id that is unique over all user managers") - public String getId( ) - { - return id; - } - - public void setId( String id ) - { - this.id = id; - } - @Override public String toString() { return "User{" + - "username='" + userId + '\'' + + "username='" + getUserId( ) + '\'' + ", fullName='" + fullName + '\'' + ", email='" + email + '\'' + ", validated=" + validated + @@ -328,7 +300,7 @@ public class UserInfo UserInfo user = (UserInfo) o; - if ( !userId.equals( user.userId ) ) + if ( !getUserId( ).equals( user.getUserId( ) ) ) { return false; } @@ -339,6 +311,6 @@ public class UserInfo @Override public int hashCode() { - return userId.hashCode(); + return getUserId( ).hashCode(); } } diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/RoleService.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/RoleService.java index 82143f4f..bbb25029 100644 --- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/RoleService.java +++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/RoleService.java @@ -20,6 +20,7 @@ package org.apache.archiva.redback.rest.api.services.v2; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.headers.Header; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -31,7 +32,6 @@ import org.apache.archiva.redback.rest.api.model.ActionStatus; import org.apache.archiva.redback.rest.api.model.Application; import org.apache.archiva.redback.rest.api.model.ApplicationRoles; import org.apache.archiva.redback.rest.api.model.RedbackRestError; -import org.apache.archiva.redback.rest.api.model.v2.AvailabilityStatus; import org.apache.archiva.redback.rest.api.model.Role; import org.apache.archiva.redback.rest.api.model.User; import org.apache.archiva.redback.rest.api.model.VerificationStatus; @@ -40,14 +40,19 @@ import org.apache.archiva.redback.rest.api.model.v2.RoleInfo; import org.apache.archiva.redback.rest.api.services.RedbackServiceException; import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; +import javax.ws.rs.HEAD; +import javax.ws.rs.PATCH; import javax.ws.rs.POST; +import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; import java.util.List; import static javax.ws.rs.core.MediaType.APPLICATION_JSON; @@ -60,15 +65,12 @@ import static org.apache.archiva.redback.rest.api.Constants.DEFAULT_PAGE_LIMIT; @Tag(name = "v2") @Tag(name = "v2/Roles") @SecurityRequirement(name = "BearerAuth") -public interface RoleManagementService +public interface RoleService { - /** - * @since 2.0 - */ @Path( "" ) @GET - @Produces( { MediaType.APPLICATION_JSON } ) + @Produces( { APPLICATION_JSON } ) @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) @Operation( summary = "Returns all roles defined. The result is paged.", parameters = { @@ -99,47 +101,181 @@ public interface RoleManagementService @QueryParam("order") @DefaultValue( "asc" ) String order) throws RedbackServiceException; - @Path( "createTemplatedRole" ) + @Path( "{roleId}" ) @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) + @Produces( { APPLICATION_JSON } ) @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) - ActionStatus createTemplatedRole( @QueryParam( "templateId" ) String templateId, - @QueryParam( "resource" ) String resource ) + @Operation( summary = "Returns information about a specific role. Use HTTP HEAD method for checking, if the resource exists.", + security = { + @SecurityRequirement( + name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION + ) + }, + responses = { + @ApiResponse( responseCode = "200", + description = "If role was found in the database", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RoleInfo.class)) + ), + @ApiResponse( responseCode = "404", description = "Role does not exist", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) + ), + @ApiResponse( responseCode = "403", description = "Authenticated user is not permitted to gather the information", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ) + } + ) + RoleInfo getRole( @PathParam( "roleId" ) String roleId ) + throws RedbackServiceException; + + @Path( "{roleId}" ) + @HEAD + @Produces( { APPLICATION_JSON } ) + @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) + @Operation( summary = "Returns information about a specific role. Use HTTP HEAD method for checking, if the resource exists.", + security = { + @SecurityRequirement( + name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION + ) + }, + responses = { + @ApiResponse( responseCode = "200", + description = "If role was found in the database" + ), + @ApiResponse( responseCode = "404", description = "Role does not exist", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) + ), + @ApiResponse( responseCode = "403", description = "Authenticated user is not permitted to gather the information", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ) + } + ) + Response checkRole( @PathParam( "roleId" ) String roleId ) + throws RedbackServiceException; + + + /** + * Moves a templated role from one resource to another resource + * @TODO: Not sure, if it makes sense to keep the child template at the source. Shouldn't we move the childs too? + * + * @param templateId the template identifier + * @param oldResource the resource of the current role + * @param newResource the resource of the new role + */ + @Path( "template/{templateId}/{oldResource}/moveto/{newResource}" ) + @POST + @Produces( {APPLICATION_JSON} ) + @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) + @Operation( summary = "Moves a templated role from one resource to another resource. If the template has child templates," + + " then child instances will be created on for the destination resource. But the child instances on the source are not deleted.", + security = { + @SecurityRequirement( + name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION + ) + }, + responses = { + @ApiResponse( responseCode = "201", + description = "If user creation was successful", + headers = { + @Header( name="Location", description = "The URL of the moved role", schema = @Schema(type="string")) + }, + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RoleInfo.class)) + ), + @ApiResponse( responseCode = "404", description = "The source role does not exist" ), + @ApiResponse( responseCode = "303", description = "The destination role exists already" ), + @ApiResponse( responseCode = "403", description = "The authenticated user has not the permission to move the role.", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ) + + } + ) + RoleInfo moveTemplatedRole( @PathParam( "templateId" ) String templateId, @PathParam( "oldResource" ) String oldResource, + @PathParam( "newResource" ) String newResource ) + throws RedbackServiceException; + + @Path( "template/{templateId}/{resource}" ) + @HEAD + @Produces( { APPLICATION_JSON} ) + @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) + @Operation( summary = "Checks, if a instance of the role template exists for the given resource", + security = { + @SecurityRequirement( + name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION + ) + }, + responses = { + @ApiResponse( responseCode = "200", + description = "If the role instance exists" + ), + @ApiResponse( responseCode = "404", description = "Role does not exist", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) + ), + @ApiResponse( responseCode = "403", description = "Authenticated user is not permitted to gather the information", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ) + } + ) + Response checkTemplateRole( @PathParam( "templateId" ) String templateId, + @PathParam( "resource" ) String resource ) + throws RedbackServiceException; + + @Path( "template/{templateId}/{resource}" ) + @PUT + @Produces( { APPLICATION_JSON } ) + @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) + @Operation( summary = "Creates a role instance from a template for the given resource", + security = { + @SecurityRequirement( + name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION + ) + }, + responses = { + @ApiResponse( responseCode = "201", + description = "If user creation was successful", + headers = { + @Header( name = "Location", description = "The URL of the created role", schema = @Schema( type = "string" ) ) + }, + content = @Content( mediaType = APPLICATION_JSON, schema = @Schema( implementation = RoleInfo.class ) ) + ), + @ApiResponse( responseCode = "200", + description = "If the role instance existed before and was updated", + headers = { + @Header( name = "Location", description = "The URL of the updated role", schema = @Schema( type = "string" ) ) + }, + content = @Content( mediaType = APPLICATION_JSON, schema = @Schema( implementation = RoleInfo.class ) ) + ), + @ApiResponse( responseCode = "404", description = "The template does not exist" ), + @ApiResponse( responseCode = "403", description = "The authenticated user has not the permission for role creation.", + content = @Content( mediaType = APPLICATION_JSON, schema = @Schema( implementation = RedbackRestError.class ) ) ) + + } + ) + RoleInfo createTemplatedRole( @PathParam( "templateId" ) String templateId, + @PathParam( "resource" ) String resource ) throws RedbackServiceException; /** - * removes a role corresponding to the role Id that was manufactured with the given resource - * + * Removes a role corresponding to the role Id that was manufactured with the given resource * it also removes any user assignments for that role * * @param templateId * @param resource */ - @Path( "removeTemplatedRole" ) - @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) + @Path( "template/{templateId}/{resource}" ) + @DELETE + @Produces( { APPLICATION_JSON } ) @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) - ActionStatus removeTemplatedRole( @QueryParam( "templateId" ) String templateId, - @QueryParam( "resource" ) String resource ) - throws RedbackServiceException; - - - /** - * allows for a role coming from a template to be renamed effectively swapping out the bits of it that - * were labeled with the oldResource with the newResource - * - * it also manages any user assignments for that role - * - * @param templateId - * @param oldResource - * @param newResource - */ - @Path( "updateRole" ) - @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) - @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) - ActionStatus updateRole( @QueryParam( "templateId" ) String templateId, @QueryParam( "oldResource" ) String oldResource, - @QueryParam( "newResource" ) String newResource ) + @Operation( summary = "Deletes a role template instance", + security = { + @SecurityRequirement( name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) + }, + responses = { + @ApiResponse( responseCode = "200", + description = "If role deletion was successful" + ), + @ApiResponse( responseCode = "404", description = "Role does not exist", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ), + @ApiResponse( responseCode = "403", description = "The authenticated user has not the permission for deletion.", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ) + } + ) + Response removeTemplatedRole( @PathParam( "templateId" ) String templateId, + @PathParam( "resource" ) String resource ) throws RedbackServiceException; @@ -147,27 +283,27 @@ public interface RoleManagementService * Assigns the role indicated by the roleId to the given principal * * @param roleId - * @param principal + * @param userId */ - @Path( "assignRole" ) - @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) + @Path( "{roleId}/assign/{userId}" ) + @PUT + @Produces( { APPLICATION_JSON } ) @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) - ActionStatus assignRole( @QueryParam( "roleId" ) String roleId, @QueryParam( "principal" ) String principal ) - throws RedbackServiceException; - - /** - * Assigns the role indicated by the roleName to the given principal - * - * @param roleName - * @param principal - * @throws RedbackServiceException - */ - @Path( "assignRoleByName" ) - @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) - @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) - ActionStatus assignRoleByName( @QueryParam( "roleName" ) String roleName, @QueryParam( "principal" ) String principal ) + @Operation( summary = "Assigns a role to a given user", + security = { + @SecurityRequirement( name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) + }, + responses = { + @ApiResponse( responseCode = "200", + description = "If the role was assigned" + ), + @ApiResponse( responseCode = "404", description = "Role does not exist", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ), + @ApiResponse( responseCode = "403", description = "The authenticated user has not the permission for role assignment.", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ) + } + ) + RoleInfo assignRole( @PathParam( "roleId" ) String roleId, @PathParam( "userId" ) String userId ) throws RedbackServiceException; /** @@ -179,11 +315,25 @@ public interface RoleManagementService * @param resource * @param principal */ - @Path( "assignTemplatedRole" ) - @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) + @Path( "template/{templateId}/{resource}/assign/{userId}" ) + @POST + @Produces( { APPLICATION_JSON } ) @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) - ActionStatus assignTemplatedRole( @QueryParam( "templateId" ) String templateId, + @Operation( summary = "Assigns a template role instance to a given user", + security = { + @SecurityRequirement( name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) + }, + responses = { + @ApiResponse( responseCode = "200", + description = "If the role instance was assigned" + ), + @ApiResponse( responseCode = "404", description = "Role instance does not exist", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ), + @ApiResponse( responseCode = "403", description = "The authenticated user has not the permission for role assignment.", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ) + } + ) + RoleInfo assignTemplatedRole( @QueryParam( "templateId" ) String templateId, @QueryParam( "resource" ) String resource, @QueryParam( "principal" ) String principal ) throws RedbackServiceException; @@ -195,161 +345,53 @@ public interface RoleManagementService * @param principal * @throws RedbackServiceException */ - @Path( "unassignRole" ) - @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) + @Path( "{roleId}/{userId}" ) + @DELETE + @Produces( { APPLICATION_JSON } ) @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) - ActionStatus unassignRole( @QueryParam( "roleId" ) String roleId, @QueryParam( "principal" ) String principal ) + @Operation( summary = "Removes a role assignment for the given role and user", + security = { + @SecurityRequirement( name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) + }, + responses = { + @ApiResponse( responseCode = "200", + description = "If the role assignment was removed" + ), + @ApiResponse( responseCode = "404", description = "Role instance does not exist", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ), + @ApiResponse( responseCode = "403", description = "The authenticated user has not the permission for role assignment.", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ) + } + ) + RoleInfo unassignRole( @QueryParam( "roleId" ) String roleId, @QueryParam( "principal" ) String principal ) throws RedbackServiceException; + /** - * Unassigns the role indicated by the role name from the given principal + * Updates a role. Attributes that are empty or null will be ignored. * - * @param roleName - * @param principal - * @throws RedbackServiceException + * @since 3.0 */ - @Path( "unassignRoleByName" ) - @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) + @Path( "{roleId}" ) + @PATCH + @Produces( { APPLICATION_JSON } ) @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) - ActionStatus unassignRoleByName( @QueryParam( "roleName" ) String roleName, @QueryParam( "principal" ) String principal ) - throws RedbackServiceException; + @Operation( summary = "Creates or updates the given role", + security = { + @SecurityRequirement( name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) + }, + responses = { + @ApiResponse( responseCode = "200", + description = "If the update was successful" + ), + @ApiResponse( responseCode = "404", description = "Role does not exist", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ), + @ApiResponse( responseCode = "403", description = "The authenticated user has not the permission for role assignment.", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ) + } + ) + RoleInfo updateRole( @QueryParam("roleId") String roleId, Role role ) + throws RedbackServiceException; - /** - * true of a role exists with the given roleId - * - * @param roleId - * @return - * @throws RedbackServiceException - */ - @Path( "roleExists" ) - @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) - @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) - AvailabilityStatus roleExists( @QueryParam( "roleId" ) String roleId ) - throws RedbackServiceException; - - /** - * true of a role exists with the given roleId - * - * @param templateId - * @param resource - * @return - * @throws RedbackServiceException - */ - @Path( "templatedRoleExists" ) - @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) - @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) - AvailabilityStatus templatedRoleExists( @QueryParam( "templateId" ) String templateId, - @QueryParam( "resource" ) String resource ) - throws RedbackServiceException; - - - /** - * Check a role template is complete in the RBAC store. - * - * @param templateId the templated role - * @param resource the resource to verify - * @throws RedbackServiceException - */ - @Path( "verifyTemplatedRole" ) - @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) - @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) - VerificationStatus verifyTemplatedRole( @QueryParam( "templateId" ) String templateId, - @QueryParam( "resource" ) String resource ) - throws RedbackServiceException; - - /** - * @since 1.4 - */ - @Path( "getEffectivelyAssignedRoles/{username}" ) - @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) - @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) - List getEffectivelyAssignedRoles( @PathParam( "username" ) String username ) - throws RedbackServiceException; - - - - - /** - * @since 2.0 - */ - @Path( "detailledAllRoles" ) - @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) - @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) - List getDetailedAllRoles() - throws RedbackServiceException; - - - /** - * @since 2.0 - */ - @Path( "getApplications/{username}" ) - @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) - @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) - List getApplications( @PathParam( "username" ) String username ) - throws RedbackServiceException; - - - /** - * @since 2.0 - */ - @Path( "getRole/{roleName}" ) - @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) - @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) - Role getRole( @PathParam( "roleName" ) String roleName ) - throws RedbackServiceException; - - /** - * @since 2.0 - */ - @Path( "updateRoleDescription" ) - @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN } ) - @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) - ActionStatus updateRoleDescription( @QueryParam( "roleName" ) String roleName, - @QueryParam( "roleDescription" ) String description ) - throws RedbackServiceException; - - /** - * update users assigned to a role - * @since 2.0 - */ - @Path( "updateRoleUsers" ) - @POST - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } ) - @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } ) - @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) - ActionStatus updateRoleUsers( Role role ) - throws RedbackServiceException; - - /** - * @since 2.0 - */ - @Path( "getApplicationRoles/{username}" ) - @GET - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } ) - @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) - List getApplicationRoles( @PathParam( "username" ) String username ) - throws RedbackServiceException; - - /** - * update roles assigned to a user - * @since 2.0 - */ - @Path( "updateUserRoles" ) - @POST - @Produces( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } ) - @Consumes( { MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML } ) - @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) - ActionStatus updateUserRoles( User user ) - throws RedbackServiceException; } diff --git a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/UserService.java b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/UserService.java index f6415bbb..243ca6e8 100644 --- a/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/UserService.java +++ b/redback-integrations/redback-rest/redback-rest-api/src/main/java/org/apache/archiva/redback/rest/api/services/v2/UserService.java @@ -31,12 +31,15 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.apache.archiva.redback.authorization.RedbackAuthorization; import org.apache.archiva.redback.integration.security.role.RedbackRoleConstants; import org.apache.archiva.redback.rest.api.model.ActionStatus; +import org.apache.archiva.redback.rest.api.model.Application; import org.apache.archiva.redback.rest.api.model.RedbackRestError; -import org.apache.archiva.redback.rest.api.model.v2.Permission; +import org.apache.archiva.redback.rest.api.model.v2.RoleTree; import org.apache.archiva.redback.rest.api.model.v2.AvailabilityStatus; import org.apache.archiva.redback.rest.api.model.v2.PagedResult; +import org.apache.archiva.redback.rest.api.model.v2.Permission; import org.apache.archiva.redback.rest.api.model.v2.PingResult; import org.apache.archiva.redback.rest.api.model.v2.RegistrationKey; +import org.apache.archiva.redback.rest.api.model.v2.RoleInfo; import org.apache.archiva.redback.rest.api.model.v2.SelfUserData; import org.apache.archiva.redback.rest.api.model.v2.User; import org.apache.archiva.redback.rest.api.model.v2.UserInfo; @@ -54,6 +57,7 @@ import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.MediaType; import java.util.Collection; import java.util.List; @@ -594,4 +598,59 @@ public interface UserService ) VerificationStatus validateUserRegistration( @PathParam( "userId" ) String userId, @PathParam( "key" ) String key ) throws RedbackServiceException; + + + /** + * Returns all roles for a given user id. Recurses all child roles. + * + * @since 3.0 + */ + @Path( "{userId}/roles" ) + @GET + @Produces( { MediaType.APPLICATION_JSON } ) + @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) + @Operation( summary = "Returns a list of all roles effectively assigned to the given user.", + responses = { + @ApiResponse( responseCode = "200", + description = "The list of roles assigned to the given user", + content = @Content(mediaType = APPLICATION_JSON, array = @ArraySchema(schema = + @Schema(implementation = org.apache.archiva.redback.rest.api.model.v2.RoleInfo.class ))) + ), + @ApiResponse( responseCode = "404", description = "User does not exist", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ), + @ApiResponse( responseCode = "403", description = "The authenticated user has not the permission for retrieving the information.", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ) + } + ) + List getEffectivelyAssignedRoles( @PathParam( "userId" ) String username ) + throws RedbackServiceException; + + + /** + * @since 3.0 + */ + @Path( "{userId}/roletree" ) + @GET + @Produces( { APPLICATION_JSON } ) + @RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) + @Operation( summary = "Returns a list of all roles that are assigned, or can be assigned to the given user. "+ + "This method sets the 'assigned' flag on all returned role objects.", + security = { + @SecurityRequirement( name = RedbackRoleConstants.USER_MANAGEMENT_RBAC_ADMIN_OPERATION ) + }, + responses = { + @ApiResponse( responseCode = "200", + description = "The list of roles separated by application that are assigned or assignable for the given user", + content = @Content(mediaType = APPLICATION_JSON, array = @ArraySchema(schema = + @Schema(implementation = Application.class ))) + ), + @ApiResponse( responseCode = "404", description = "User does not exist", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ), + @ApiResponse( responseCode = "403", description = "The authenticated user has not the permission for retrieving the information.", + content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) ) + } + ) + RoleTree getRoleTree( @PathParam( "userId" ) String username ) + throws RedbackServiceException; + } diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultRoleManagementService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultRoleManagementService.java index a80439bb..40ec9a3b 100644 --- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultRoleManagementService.java +++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/DefaultRoleManagementService.java @@ -29,7 +29,6 @@ import org.apache.archiva.redback.rbac.UserAssignment; import org.apache.archiva.redback.rest.api.model.ActionStatus; import org.apache.archiva.redback.rest.api.model.Application; import org.apache.archiva.redback.rest.api.model.ApplicationRoles; -import org.apache.archiva.redback.rest.api.model.v2.AvailabilityStatus; import org.apache.archiva.redback.rest.api.model.ErrorMessage; import org.apache.archiva.redback.rest.api.model.Role; import org.apache.archiva.redback.rest.api.model.RoleTemplate; @@ -127,7 +126,7 @@ public class DefaultRoleManagementService { try { - roleManager.updateRole( templateId, oldResource, newResource ); + roleManager.moveTemplatedRole( templateId, oldResource, newResource ); } catch ( RoleManagerException e ) { @@ -391,7 +390,7 @@ public class DefaultRoleManagementService org.apache.archiva.redback.rbac.Role rbacRole = rbacManager.getRole( roleName ); Role role = new Role( rbacRole ); - Map parentRoles = rbacManager.getParentRoles( rbacRole ); + Map parentRoles = rbacManager.getParentRoleNames( rbacRole ); for ( String parentRoleName : parentRoles.keySet() ) { role.getParentRoleNames().add( parentRoleName ); diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/BaseRedbackService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/BaseRedbackService.java new file mode 100644 index 00000000..63318bb5 --- /dev/null +++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/BaseRedbackService.java @@ -0,0 +1,145 @@ +package org.apache.archiva.redback.rest.services.v2; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + */ + +import org.apache.archiva.redback.rbac.RBACManager; +import org.apache.archiva.redback.rbac.RbacManagerException; +import org.apache.archiva.redback.rbac.Role; +import org.apache.archiva.redback.rest.api.MessageKeys; +import org.apache.archiva.redback.rest.api.model.ErrorMessage; +import org.apache.archiva.redback.rest.api.model.v2.BaseUserInfo; +import org.apache.archiva.redback.rest.api.model.v2.RoleInfo; +import org.apache.archiva.redback.rest.api.services.RedbackServiceException; +import org.apache.archiva.redback.users.User; +import org.apache.archiva.redback.users.UserManager; +import org.apache.archiva.redback.users.UserManagerException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.inject.Named; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * @author Martin Stockhammer + */ +public class BaseRedbackService +{ + private static final Logger log = LoggerFactory.getLogger( BaseRedbackService.class ); + + protected RBACManager rbacManager; + protected UserManager userManager; + + public BaseRedbackService( @Named( value = "rbacManager#default" ) RBACManager rbacManager, @Named( value = "userManager#default" ) UserManager userManager ) + { + this.rbacManager = rbacManager; + this.userManager = userManager; + } + + protected RoleInfo getRoleInfo( org.apache.archiva.redback.rbac.Role rbacRole ) throws RedbackServiceException + { + try + { + RoleInfo role = RoleInfo.of( rbacRole ); + role.setParentRoleIds( getParentRoles( rbacRole ) ); + role.setChildRoleIds( getChildRoles( rbacRole ) ); + role.setAssignedUsers( getAssignedUsersRecursive( rbacRole ) ); + return role; + } + catch ( RbacManagerException e ) + { + log.error( "Error while retrieving role information {}", e.getMessage( ), e ); + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) ); + } + } + + protected List getParentRoles( org.apache.archiva.redback.rbac.Role rbacRole ) throws RbacManagerException + { + return new ArrayList<>( rbacManager.getParentRoleIds( rbacRole ).keySet( )); + } + + protected List getChildRoles( Role rbacRole) throws RbacManagerException + { + return new ArrayList<>( rbacManager.getChildRoleIds( rbacRole ).keySet( ) ); + } + + protected List getAssignedUsersRecursive( org.apache.archiva.redback.rbac.Role rbacRole ) throws RbacManagerException + { + try + { + return rbacManager.getUserAssignmentsForRoles( recurseRoles( rbacRole ).map( role -> role.getName( ) ).collect( Collectors.toList( ) ) ) + .stream( ).map( assignment -> getUserInfo( assignment.getPrincipal( ) ) ).collect( Collectors.toList( ) ); + } + catch ( RuntimeException e ) + { + log.error( "Could not recurse roles for assignments {}", e.getMessage( ) ); + throw new RbacManagerException( e.getCause( ) ); + } + } + + private Stream recurseRoles( Role startRole ) + { + return Stream.concat( Stream.of( startRole ), getParentRoleStream( startRole ).flatMap( this::recurseRoles ) ).distinct( ); + } + + private Stream getParentRoleStream( Role role ) + { + try + { + return rbacManager.getParentRoleNames( role ).values( ).stream( ); + } + catch ( RbacManagerException e ) + { + throw new RuntimeException( e ); + } + } + + BaseUserInfo getUserInfo( String userId ) + { + try + { + User user = userManager.findUser( userId ); + return new BaseUserInfo( user.getId( ), user.getUsername( ) ); + } + catch ( UserManagerException e ) + { + throw new RuntimeException( e ); + } + } + + protected Optional getRoleInfoOptional( Role rbacRole ) + { + try + { + RoleInfo role = RoleInfo.of( rbacRole ); + role.setParentRoleIds( getParentRoles( rbacRole ) ); + role.setChildRoleIds( getChildRoles( rbacRole ) ); + role.setAssignedUsers( getAssignedUsersRecursive( rbacRole ) ); + return Optional.of( role ); + } + catch ( RbacManagerException e ) + { + log.error( "Error while retrieving role information {}", e.getMessage( ), e ); + return Optional.empty( ); + } + } +} diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultGroupService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultGroupService.java index c512194c..a495d427 100644 --- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultGroupService.java +++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultGroupService.java @@ -61,6 +61,7 @@ import java.util.stream.Collectors; * @author Martin Stockhammer * @since 3.0 */ +@SuppressWarnings( "SpringJavaAutowiredFieldsWarningInspection" ) @Service("v2.groupService#rest") public class DefaultGroupService implements GroupService @@ -85,7 +86,10 @@ public class DefaultGroupService @Named(value = "ldapConnectionFactory#configurable") private LdapConnectionFactory ldapConnectionFactory; - private static final Group getGroupFromLdap( LdapGroup ldapGroup ) { + public DefaultGroupService( ) { + } + + private static Group getGroupFromLdap( LdapGroup ldapGroup ) { Group group = new Group( ); group.setName( ldapGroup.getName() ); group.setUniqueName( ldapGroup.getDn() ); @@ -128,7 +132,7 @@ public class DefaultGroupService * be found, it will set "" for the uniqueName * * @return the list of mapping - * @throws RedbackServiceException + * @throws RedbackServiceException if there was an error retrieving the mapping data */ @Override public List getGroupMappings() diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultRoleService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultRoleService.java new file mode 100644 index 00000000..a4077b7e --- /dev/null +++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultRoleService.java @@ -0,0 +1,434 @@ +package org.apache.archiva.redback.rest.services.v2; +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +import org.apache.archiva.redback.integration.security.role.RedbackRoleConstants; +import org.apache.archiva.redback.integration.util.RoleSorter; +import org.apache.archiva.redback.rbac.Permission; +import org.apache.archiva.redback.rbac.RBACManager; +import org.apache.archiva.redback.rbac.RbacManagerException; +import org.apache.archiva.redback.rbac.RbacObjectNotFoundException; +import org.apache.archiva.redback.rbac.Resource; +import org.apache.archiva.redback.rest.api.MessageKeys; +import org.apache.archiva.redback.rest.api.model.ErrorMessage; +import org.apache.archiva.redback.rest.api.model.Role; +import org.apache.archiva.redback.rest.api.model.RoleTemplate; +import org.apache.archiva.redback.rest.api.model.v2.PagedResult; +import org.apache.archiva.redback.rest.api.model.v2.RoleInfo; +import org.apache.archiva.redback.rest.api.services.RedbackServiceException; +import org.apache.archiva.redback.rest.api.services.v2.RoleService; +import org.apache.archiva.redback.rest.services.RedbackAuthenticationThreadLocal; +import org.apache.archiva.redback.rest.services.RedbackRequestInformation; +import org.apache.archiva.redback.role.RoleExistsException; +import org.apache.archiva.redback.role.RoleManager; +import org.apache.archiva.redback.role.RoleManagerException; +import org.apache.archiva.redback.role.RoleNotFoundException; +import org.apache.archiva.redback.role.model.ModelTemplate; +import org.apache.archiva.redback.users.User; +import org.apache.archiva.redback.users.UserManager; +import org.apache.archiva.redback.users.UserManagerException; +import org.apache.archiva.redback.users.UserNotFoundException; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +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.function.BiPredicate; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * @author Olivier Lamy + * @since 1.3 + */ +@Service("v2.roleService#rest") +public class DefaultRoleService extends BaseRedbackService + implements RoleService +{ + + private Logger log = LoggerFactory.getLogger( DefaultRoleService.class ); + + private RoleManager roleManager; + + @Context + private HttpServletRequest httpServletRequest; + + @Context + private HttpServletResponse httpServletResponse; + + @Context + private UriInfo uriInfo; + + private static final String[] DEFAULT_SEARCH_FIELDS = {"name", "description"}; + private static final Map> FILTER_MAP = new HashMap<>( ); + private static final Map> ORDER_MAP = new HashMap<>( ); + private static final QueryHelper QUERY_HELPER; + + static + { + + QUERY_HELPER = new QueryHelper<>( FILTER_MAP, ORDER_MAP, DEFAULT_SEARCH_FIELDS ); + QUERY_HELPER.addStringFilter( "name", org.apache.archiva.redback.rbac.Role::getName ); + QUERY_HELPER.addStringFilter( "description", org.apache.archiva.redback.rbac.Role::getDescription ); + QUERY_HELPER.addBooleanFilter( "assignable", org.apache.archiva.redback.rbac.Role::isAssignable ); + + // The simple Comparator.comparing(attribute) is not null safe + // As there are attributes that may have a null value, we have to use a comparator with nullsLast(naturalOrder) + // and the wrapping Comparator.nullsLast(Comparator.comparing(attribute)) does not work, because the attribute is not checked by the nullsLast-Comparator + QUERY_HELPER.addNullsafeFieldComparator( "name", org.apache.archiva.redback.rbac.Role::getName ); + QUERY_HELPER.addNullsafeFieldComparator( "id", org.apache.archiva.redback.rbac.Role::getId ); + QUERY_HELPER.addNullsafeFieldComparator( "resource", org.apache.archiva.redback.rbac.Role::getResource ); + QUERY_HELPER.addNullsafeFieldComparator( "assignable", org.apache.archiva.redback.rbac.Role::isAssignable ); + QUERY_HELPER.addNullsafeFieldComparator( "template_instance", org.apache.archiva.redback.rbac.Role::isTemplateInstance ); + } + + @Inject + public DefaultRoleService( RoleManager roleManager, + @Named(value = "rbacManager#default") RBACManager rbacManager, + @Named(value = "userManager#default") UserManager userManager ) + { + super( rbacManager, userManager ); + this.roleManager = roleManager; + + log.debug( "use rbacManager impl: {}", rbacManager.getClass().getName() ); + log.debug( "use userManager impl: {}", userManager.getClass().getName() ); + } + + @Override + public PagedResult getAllRoles( String searchTerm, Integer offset, Integer limit, List orderBy, String order ) throws RedbackServiceException + { + boolean ascending = !"desc".equals( order ); + try + { + // UserQuery does not work here, because the configurable user manager does only return the query for + // the first user manager in the list. So we have to fetch the whole role list + List rawRoles = rbacManager.getAllRoles( ); + Predicate filter = QUERY_HELPER.getQueryFilter( searchTerm ); + long size = rawRoles.stream( ).filter( filter ).count( ); + List users = rawRoles.stream( ) + .filter( filter ) + .sorted( QUERY_HELPER.getComparator( orderBy, ascending ) ).skip( offset ).limit( limit ) + .map( role -> { + try + { + return Optional.of( getRoleInfo( role ) ); + } + catch ( RedbackServiceException e ) + { + return Optional.empty(); + } + } ).filter(Optional::isPresent) + .map(Optional::get) + .collect( Collectors.toList( ) ); + return new PagedResult<>( (int) size, offset, limit, users ); + } + catch ( RbacManagerException e ) + { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL , e.getMessage( )) ); + } + + } + + @Override + public RoleInfo getRole( String roleId ) throws RedbackServiceException + { + try + { + org.apache.archiva.redback.rbac.Role rbacRole = rbacManager.getRoleById( roleId ); + RoleInfo role = getRoleInfo( rbacRole ); + return role; + } + catch ( RbacObjectNotFoundException e ) + { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_NOT_FOUND, roleId ), 404 ); + } + catch ( RbacManagerException e ) + { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) ); + } + } + + @Override + public Response checkRole( String roleId ) throws RedbackServiceException + { + try + { + org.apache.archiva.redback.rbac.Role rbacRole = rbacManager.getRoleById( roleId ); + if (rbacRole==null) { + return Response.status( 404 ).build(); + } else + { + return Response.ok( ).build( ); + } + } + catch ( RbacObjectNotFoundException e ) + { + return Response.status( 404 ).build(); + } + catch ( RbacManagerException e ) + { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) ); + } + } + + + + @Override + public RoleInfo moveTemplatedRole( String templateId, String oldResource, String newResource ) + throws RedbackServiceException + { + try + { + if (StringUtils.isEmpty( templateId ) || StringUtils.isEmpty( oldResource ) || StringUtils.isEmpty( newResource )) { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_NOT_FOUND ), 404 ); + } + boolean sourceExists = roleManager.templatedRoleExists( templateId, oldResource ); + if (!sourceExists) { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_INSTANCE_NOT_FOUND, templateId, oldResource ), 404 ); + } + boolean destExists = roleManager.templatedRoleExists( templateId, newResource ); + if (destExists) { + httpServletResponse.setHeader( "Location", uriInfo.getAbsolutePathBuilder().path("../../..").path(newResource).build( ).normalize().toString() ); + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_INSTANCE_EXISTS, templateId, newResource ), 303 ); + } + String roleId = roleManager.moveTemplatedRole( templateId, oldResource, newResource ); + httpServletResponse.setHeader( "Location", uriInfo.getAbsolutePathBuilder().path("../../..").path(newResource).build( ).normalize().toString() ); + httpServletResponse.setStatus( 201 ); + return getRoleInfo( rbacManager.getRoleById( roleId ) ); + } + catch ( RoleExistsException e ) { + httpServletResponse.setHeader( "Location", uriInfo.getAbsolutePathBuilder().path("../../..").path(newResource).build( ).normalize().toString() ); + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_INSTANCE_EXISTS, templateId, newResource ), 303 ); + } + catch ( RoleManagerException e ) + { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLEMANAGER_FAIL, e.getMessage( ) ) ); + } + catch ( RbacManagerException e ) + { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) ); + } + } + + + @Override + public Response checkTemplateRole( String templateId, String resource ) + throws RedbackServiceException + { + try + { + if (roleManager.templatedRoleExists( templateId, resource )) { + return Response.ok( ).build( ); + } else { + return Response.status( 404 ).build(); + } + } + catch ( RoleManagerException e ) + { + throw new RedbackServiceException( e.getMessage() ); + } + + } + + @Override + public RoleInfo createTemplatedRole( String templateId, String resource ) + throws RedbackServiceException + { + if (StringUtils.isEmpty( templateId )) { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_NOT_FOUND ), 404 ); + } + if (StringUtils.isEmpty( resource )) { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_NOT_FOUND ), 404 ); + } + try + { + boolean exists = roleManager.templatedRoleExists( templateId, resource ); + String roleId = roleManager.createTemplatedRole( templateId, resource ); + httpServletResponse.setHeader( "Location", uriInfo.getAbsolutePathBuilder().path("../../..").path(roleId).build( ).normalize().toString() ); + if (exists) + { + httpServletResponse.setStatus( 200 ); + } else { + httpServletResponse.setStatus( 201 ); + } + return getRoleInfo( rbacManager.getRoleById( roleId ) ); + } catch (RoleNotFoundException e) { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_NOT_FOUND, templateId, resource ), 404 ); + } catch (RoleExistsException e) { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_INSTANCE_EXISTS, templateId, resource ), 303 ); + } + catch ( RoleManagerException e ) + { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLEMANAGER_FAIL, e.getMessage( ) ) ); + } + catch ( RbacManagerException e ) + { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) ); + } + } + + @Override + public Response removeTemplatedRole( String templateId, String resource ) + throws RedbackServiceException + { + + try + { + roleManager.removeTemplatedRole( templateId, resource ); + return Response.ok( ).build( ); + } + catch ( RoleNotFoundException e ) { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_INSTANCE_NOT_FOUND, templateId, resource ), 404 ); + } + catch ( RoleManagerException e ) + { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLEMANAGER_FAIL, e.getMessage( ) ) ); + } + } + + + + @Override + public RoleInfo assignRole( String roleId, String userId ) + throws RedbackServiceException + { + try + { + userManager.findUser( userId ); + roleManager.assignRole( roleId, userId ); + return getRoleInfo( rbacManager.getRoleById( roleId ) ); + } + catch ( RoleNotFoundException e ) { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLE_NOT_FOUND, e.getMessage( ) ), 404 ); + } + catch ( RoleManagerException e ) + { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_ROLEMANAGER_FAIL, e.getMessage( ) ) ); + } + catch ( UserNotFoundException e ) + { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_USER_NOT_FOUND, e.getMessage( ) ), 404 ); + } + catch ( UserManagerException e ) + { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_USERMANAGER_FAIL, e.getMessage( ) ) ); + } + catch ( RbacObjectNotFoundException e ) + { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) ); + } + catch ( RbacManagerException e ) + { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) ); + } + } + + + @Override + public RoleInfo assignTemplatedRole( String templateId, String resource, String principal ) + throws RedbackServiceException + { + try + { + roleManager.assignTemplatedRole( templateId, resource, principal ); + } + catch ( RoleManagerException e ) + { + throw new RedbackServiceException( e.getMessage() ); + } + return null; + } + + @Override + public RoleInfo unassignRole( String roleId, String principal ) + throws RedbackServiceException + { + try + { + roleManager.unassignRole( roleId, principal ); + } + catch ( RoleManagerException e ) + { + throw new RedbackServiceException( e.getMessage() ); + } + return null; + } + + @Override + public RoleInfo updateRole( String roleId, Role role ) throws RedbackServiceException + { + return null; + } + + + +// public List getEffectivelyAssignedRoles( String username ) +// throws RedbackServiceException +// { +// if ( StringUtils.isEmpty( username ) ) +// { +// throw new RedbackServiceException( new ErrorMessage( "user.cannot.be.null" ) ); +// } +// try +// { +// List roles = +// filterAssignableRoles( rbacManager.getEffectivelyAssignedRoles( username ) ); +// +// List effectivelyAssignedRoles = new ArrayList( roles.size() ); +// +// for ( org.apache.archiva.redback.rbac.Role r : roles ) +// { +// effectivelyAssignedRoles.add( new Role( r ) ); +// } +// +// Collections.sort( effectivelyAssignedRoles, RoleComparator.INSTANCE ); +// +// return effectivelyAssignedRoles; +// } +// catch ( RbacManagerException rme ) +// { +// // ignore, this can happen when the user has no roles assigned +// } +// return new ArrayList( 0 ); +// } + + + //---------------------------------------------------------------- + // Internal methods + //---------------------------------------------------------------- + + +} diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultUserService.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultUserService.java index b4658094..5561d5c6 100644 --- a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultUserService.java +++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/DefaultUserService.java @@ -41,17 +41,22 @@ import org.apache.archiva.redback.policy.PasswordRuleViolationException; import org.apache.archiva.redback.policy.UserSecurityPolicy; import org.apache.archiva.redback.rbac.RBACManager; import org.apache.archiva.redback.rbac.RbacManagerException; +import org.apache.archiva.redback.rbac.Role; import org.apache.archiva.redback.rbac.UserAssignment; import org.apache.archiva.redback.rest.api.MessageKeys; import org.apache.archiva.redback.rest.api.model.ActionStatus; import org.apache.archiva.redback.rest.api.model.ErrorMessage; +import org.apache.archiva.redback.rest.api.model.v2.Application; import org.apache.archiva.redback.rest.api.model.v2.AvailabilityStatus; +import org.apache.archiva.redback.rest.api.model.v2.BaseRoleInfo; import org.apache.archiva.redback.rest.api.model.v2.Operation; import org.apache.archiva.redback.rest.api.model.v2.PagedResult; import org.apache.archiva.redback.rest.api.model.v2.Permission; import org.apache.archiva.redback.rest.api.model.v2.PingResult; import org.apache.archiva.redback.rest.api.model.v2.RegistrationKey; import org.apache.archiva.redback.rest.api.model.v2.Resource; +import org.apache.archiva.redback.rest.api.model.v2.RoleInfo; +import org.apache.archiva.redback.rest.api.model.v2.RoleTree; import org.apache.archiva.redback.rest.api.model.v2.SelfUserData; import org.apache.archiva.redback.rest.api.model.v2.User; import org.apache.archiva.redback.rest.api.model.v2.UserInfo; @@ -65,6 +70,7 @@ import org.apache.archiva.redback.rest.services.interceptors.RedbackPrincipal; import org.apache.archiva.redback.rest.services.utils.PasswordValidator; import org.apache.archiva.redback.role.RoleManager; import org.apache.archiva.redback.role.RoleManagerException; +import org.apache.archiva.redback.role.model.ModelApplication; import org.apache.archiva.redback.system.SecuritySession; import org.apache.archiva.redback.system.SecuritySystem; import org.apache.archiva.redback.users.UserManager; @@ -91,17 +97,19 @@ import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.function.BiPredicate; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.stream.Stream; @Service( "v2.userService#rest" ) -public class DefaultUserService +public class DefaultUserService extends BaseRedbackService implements UserService { @@ -113,6 +121,7 @@ public class DefaultUserService private static final String[] DEFAULT_SEARCH_FIELDS = {"user_id", "full_name", "email"}; private static final Map> FILTER_MAP = new HashMap<>( ); private static final Map> ORDER_MAP = new HashMap<>( ); + private static final QueryHelper QUERY_HELPER; static { @@ -133,11 +142,10 @@ public class DefaultUserService FILTER_MAP.put( "user_id", ( String q, org.apache.archiva.redback.users.User u ) -> StringUtils.containsIgnoreCase( u.getUsername( ), q ) ); FILTER_MAP.put( "full_name", ( String q, org.apache.archiva.redback.users.User u ) -> StringUtils.containsIgnoreCase( u.getFullName( ), q ) ); FILTER_MAP.put( "email", ( String q, org.apache.archiva.redback.users.User u ) -> StringUtils.containsIgnoreCase( u.getEmail( ), q ) ); + + QUERY_HELPER = new QueryHelper<>( FILTER_MAP, ORDER_MAP, DEFAULT_SEARCH_FIELDS ); } - - private UserManager userManager; - private SecuritySystem securitySystem; @Inject @@ -174,9 +182,10 @@ public class DefaultUserService @Inject private Mailer mailer; + @Inject - @Named( value = "rbacManager#default" ) - private RBACManager rbacManager; + @Named( value = "v2.roleService#rest" ) + private DefaultRoleService roleManagementService; private HttpAuthenticator httpAuthenticator; @@ -196,10 +205,11 @@ public class DefaultUserService private SecurityContext securityContext; @Inject - public DefaultUserService( @Named( value = "userManager#default" ) UserManager userManager, + public DefaultUserService(@Named( value = "rbacManager#default" ) RBACManager rbacManager, + @Named( value = "userManager#default" ) UserManager userManager , SecuritySystem securitySystem ) { - this.userManager = userManager; + super( rbacManager, userManager ); this.securitySystem = securitySystem; } @@ -220,7 +230,8 @@ public class DefaultUserService public UserInfo createUser( User user ) throws RedbackServiceException { - if (user==null) { + if ( user == null ) + { throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_USER_ID_EMPTY ), 422 ); } UserInfo result; @@ -311,7 +322,8 @@ public class DefaultUserService public void deleteUser( String userId ) throws RedbackServiceException { - if (StringUtils.isEmpty( userId )) { + if ( StringUtils.isEmpty( userId ) ) + { throw new RedbackServiceException( MessageKeys.ERR_USER_ID_EMPTY, 404 ); } @@ -355,7 +367,8 @@ public class DefaultUserService public UserInfo getUser( String userId ) throws RedbackServiceException { - if (StringUtils.isEmpty( userId)) { + if ( StringUtils.isEmpty( userId ) ) + { throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_USER_ID_EMPTY ), 404 ); } try @@ -377,55 +390,6 @@ public class DefaultUserService } } - Comparator getAttributeComparator( String attributeName ) - { - return ORDER_MAP.get( attributeName ); - } - - Comparator getComparator( List orderBy, boolean ascending ) - { - if ( ascending ) - { - return orderBy.stream( ).map( ( String name ) -> getAttributeComparator( name ) ).filter( Objects::nonNull ).reduce( Comparator::thenComparing ).get( ); - } - else - { - return orderBy.stream( ).map( ( String name ) -> getAttributeComparator( name ) == null ? null : getAttributeComparator( name ).reversed( ) ).filter( Objects::nonNull ).reduce( Comparator::thenComparing ).get( ); - } - } - - static Predicate getFilter( final String attribute, final String queryToken ) - { - if ( FILTER_MAP.containsKey( attribute ) ) - { - return ( org.apache.archiva.redback.users.User u ) -> FILTER_MAP.get( attribute ).test( queryToken, u ); - } - else - { - return Arrays.stream( DEFAULT_SEARCH_FIELDS ) - .map( att -> getFilter( att, queryToken ) ).reduce( Predicate::or ).get( ); - } - } - - Predicate getUserFilter( String queryTerms ) - { - return Arrays.stream( queryTerms.split( "\\s+" ) ) - .map( s -> { - if ( s.contains( ":" ) ) - { - String attr = StringUtils.substringBefore( s, ":" ); - String term = StringUtils.substringAfter( s, ":" ); - return getFilter( attr, term ); - } - else - { - return Arrays.stream( DEFAULT_SEARCH_FIELDS ) - .map( att -> getFilter( att, s ) ).reduce( Predicate::or ).get( ); - } - } - ).reduce( Predicate::or ).get( ); - } - @Override public PagedResult getUsers( String q, Integer offset, Integer limit, List orderBy, String order ) @@ -437,11 +401,11 @@ public class DefaultUserService // UserQuery does not work here, because the configurable user manager does only return the query for // the first user manager in the list. So we have to fetch the whole user list List rawUsers = userManager.getUsers( ); - Predicate filter = getUserFilter( q ); + Predicate filter = QUERY_HELPER.getQueryFilter( q ); long size = rawUsers.stream( ).filter( filter ).count( ); List users = rawUsers.stream( ) .filter( filter ) - .sorted( getComparator( orderBy, ascending ) ).skip( offset ).limit( limit ) + .sorted( QUERY_HELPER.getComparator( orderBy, ascending ) ).skip( offset ).limit( limit ) .map( user -> getRestUser( user ) ) .collect( Collectors.toList( ) ); return new PagedResult<>( (int) size, offset, limit, users ); @@ -974,6 +938,101 @@ public class DefaultUserService } } + @Override + public List getEffectivelyAssignedRoles( String username ) throws RedbackServiceException + { + try + { + return rbacManager.getEffectivelyAssignedRoles( username ).stream( ) + .filter( org.apache.archiva.redback.rbac.Role::isAssignable ) + .map( this::getRoleInfoOptional ) + .filter( Optional::isPresent ) + .map(Optional::get).collect( Collectors.toList()); + } + catch ( RbacManagerException e ) + { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) ); + } + } + + private static final Application toApplication( ModelApplication app ) + { + Application application = new Application( ); + application.setId( app.getId( ) ); + application.setVersion( app.getVersion( ) ); + application.setDescription( app.getDescription( ) == null ? "" :app.getDescription() ); + application.setLongDescription( app.getLongDescription( ) == null ? "" : app.getLongDescription( ) ); + return application; + } + + private List getAllApplications( ) + { + return roleManager.getModel( ).getApplications( ).stream( ).map( DefaultUserService::toApplication ).collect( Collectors.toList( ) ); + } + + + @Override + public RoleTree getRoleTree( final String username ) throws RedbackServiceException + { + final Map roleApplicationMap = roleManager.getModel( ).getApplications( ).stream( ) + .flatMap( modelApplication -> modelApplication.getRoles( ).stream( ).map( role -> { + BaseRoleInfo roleInfo = new BaseRoleInfo( ); + roleInfo.setId( role.getId( ) ); + roleInfo.setApplicationId( modelApplication.getId( ) ); + return roleInfo; + } ) ).collect( Collectors.toMap( BaseRoleInfo::getId, BaseRoleInfo::getApplicationId ) ); + + try + { + final Set assignedRoleNames = new HashSet( rbacManager.getUserAssignment( username ).getRoleNames( ) ); + // We have to reuse the BaseRoleInfo objects, because the roles are not returned starting from the roots + final Map roleNameCache = new HashMap<>( ); + List roleList = rbacManager.getAllRoles( ).stream( ).flatMap( this::flattenRole ).map( role -> + { + BaseRoleInfo roleInfo = roleNameCache.computeIfAbsent( role.getName( ), s -> new BaseRoleInfo( ) ); + // Setting the role data, as there may be child role objects that are not completely initialized + roleInfo = BaseRoleInfo.of( role, roleInfo ); + roleInfo.setApplicationId( roleApplicationMap.get( role.getId( ) ) ); + roleInfo.setAssigned( assignedRoleNames.contains( role.getName( ) ) ); + roleInfo.setChildren( role.getChildRoleNames( ).stream( ) + .map( roleName -> + { + BaseRoleInfo childRoleInfo = roleNameCache.computeIfAbsent( roleName, s -> BaseRoleInfo.ofName( roleName ) ); + childRoleInfo.setChild( true ); + return childRoleInfo; + } ) + .collect( Collectors.toList( ) ) ); + return roleInfo; + } ).collect( Collectors.toList( ) ); + RoleTree roleTree = new RoleTree( ); + roleTree.setApplications( getAllApplications( ).stream( ).collect( Collectors.toMap( Application::getId, Function.identity( ) ) ) ); + roleTree.setRootRoles( roleList.stream( ).filter( BaseRoleInfo::isNotChild ).collect( Collectors.toList( ) ) ); + roleTree.setUserId( username ); + return roleTree; + } + catch ( RbacManagerException e ) + { + throw new RedbackServiceException( ErrorMessage.of( MessageKeys.ERR_RBACMANAGER_FAIL, e.getMessage( ) ) ); + } + } + + private Stream flattenRole( Role role ) + { + return Stream.concat( Stream.of( role ), this.getChildren( role ).flatMap( this::flattenRole ) ).distinct( ); + } + + private Stream getChildren( Role role ) + { + try + { + return rbacManager.getChildRoleNames( role ).values( ).stream( ); + } + catch ( RbacManagerException e ) + { + throw new RuntimeException( e ); + } + } + @Override public Collection getUserOperations( String userName ) throws RedbackServiceException diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/QueryHelper.java b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/QueryHelper.java new file mode 100644 index 00000000..ba417aa1 --- /dev/null +++ b/redback-integrations/redback-rest/redback-rest-services/src/main/java/org/apache/archiva/redback/rest/services/v2/QueryHelper.java @@ -0,0 +1,168 @@ +package org.apache.archiva.redback.rest.services.v2;/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + */ + +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * + * Helper class that returns combined filter and comparison objects for ordering. + * + * The query term may be consist of simple query terms separated by whitespace or attribute queries + * in the form attribute:query, which means only the attribute is searched for the query string. + *
+ * Example: + *
+ *
`user1 test`
+ *
+ * searches for the tokens user1 and test in the default attributes. + *
+ *
`user1 name:test`
+ *
searches for the token user1 in the default attributes and for the token test in the attribute name.
+ *
+ * + * + * @since 3.0 + * @author Martin Stockhammer + */ +public class QueryHelper +{ + + private final Map> FILTER_MAP; + private final Map> ORDER_MAP; + private final String[] DEFAULT_SEARCH_FIELDS; + private final Predicate DEFAULT_FILTER = ( T att ) -> false; + + + /** + * Creates a new query helper with the given filters and comparators. + * + * @param filterMap a map of filters, where the key is the attribute name and the value is a predicate that matches + * the filter value and the object instance. + * @param orderMap a map of comparators, where key is the attribute name and the value is a comparator for the given + * object instance + * @param defaultSearchFields A array of attribute names, that are used as default search fields. + */ + public QueryHelper(Map> filterMap, Map> orderMap, + String[] defaultSearchFields) + { + this.FILTER_MAP = filterMap; + this.DEFAULT_SEARCH_FIELDS = defaultSearchFields; + this.ORDER_MAP = new HashMap<>( orderMap ); + } + + public > void addNullsafeFieldComparator( String fieldName, Function keyExtractor) { + ORDER_MAP.put( fieldName, Comparator.comparing( keyExtractor, Comparator.nullsLast( Comparator.naturalOrder( ) ) ) ); + } + + public void addStringFilter(String attribute, Function keyExtractor) { + this.FILTER_MAP.put( attribute, ( String q, T r ) -> StringUtils.containsIgnoreCase( keyExtractor.apply( r ), q ) ); + } + + public void addBooleanFilter(String attribute, Function keyExtractor) { + this.FILTER_MAP.put( attribute, ( String q, T r ) -> Boolean.valueOf( q ) == keyExtractor.apply( r ) ); + } + + /** + * Get the comparator for a specific attribute. + * @param attributeName the name of the attribute. + * @return + */ + Comparator getAttributeComparator( String attributeName ) + { + return ORDER_MAP.get( attributeName ); + } + + /** + * Get the combined order for the given attributes in the given order. + * + * @param orderBy the attributes to compare. The first attribute in the list will be used first for comparing. + * @param ascending + * @return + */ + Comparator getComparator( List orderBy, boolean ascending ) + { + if ( ascending ) + { + return orderBy.stream( ).map( ( String name ) -> getAttributeComparator( name ) ).filter( Objects::nonNull ) + .reduce( Comparator::thenComparing ) + .orElseThrow( () -> new IllegalArgumentException( "No attribute ordering found" ) ); + } + + else + { + return orderBy.stream( ).map( ( String name ) -> getAttributeComparator( name ) == null ? null : getAttributeComparator( name ) + .reversed( ) ).filter( Objects::nonNull ).reduce( Comparator::thenComparing ) + .orElseThrow( () -> new IllegalArgumentException( "No attribute ordering found" ) ); + } + } + + /** + * Returns a query filter for a specific attribute and query token. + * @param attribute the attribute name to filter for. + * @param queryToken the search token. + * @return The predicate used to filter the token + */ + Predicate getAttributeQueryFilter( final String attribute, final String queryToken ) + { + if ( FILTER_MAP.containsKey( attribute ) ) + { + return ( T u ) -> FILTER_MAP.get( attribute ).test( queryToken, u ); + } + else + { + return DEFAULT_FILTER; + } + } + + /** + * Returns the combined query filter for the given query terms. + * The query terms may be either simple strings separated by whitespace or use the + * attribute:query syntax, that searches only the attribute for the query term. + * @param queryTerms the query string + * @return the combined query filter + */ + Predicate getQueryFilter( String queryTerms ) + { + return Arrays.stream( queryTerms.split( "\\s+" ) ) + .map( s -> { + if ( s.contains( ":" ) ) + { + String attr = StringUtils.substringBefore( s, ":" ); + String term = StringUtils.substringAfter( s, ":" ); + return getAttributeQueryFilter( attr, term ); + } + else + { + return Arrays.stream( DEFAULT_SEARCH_FIELDS ) + .map( att -> getAttributeQueryFilter( att, s ) ).reduce( Predicate::or ).get( ); + } + } + ).reduce( Predicate::or ).get( ); + } + +} diff --git a/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml b/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml index 5041bc4e..ff90b959 100644 --- a/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml +++ b/redback-integrations/redback-rest/redback-rest-services/src/main/resources/META-INF/spring-context.xml @@ -93,7 +93,7 @@ - + diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractNativeRestServices.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractNativeRestServices.java index 2adc1e76..15233871 100644 --- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractNativeRestServices.java +++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/AbstractNativeRestServices.java @@ -128,7 +128,7 @@ public abstract class AbstractNativeRestServices } - private String getServiceBasePath( ) + protected String getServiceBasePath( ) { return "/v2/redback"; } diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeRoleServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeRoleServiceTest.java new file mode 100644 index 00000000..f3105d37 --- /dev/null +++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeRoleServiceTest.java @@ -0,0 +1,505 @@ +package org.apache.archiva.redback.rest.services.v2; + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + */ + +import io.restassured.response.Response; +import org.apache.archiva.redback.rest.api.model.v2.RoleInfo; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.MethodOrderer; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestMethodOrder; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static io.restassured.RestAssured.given; +import static io.restassured.http.ContentType.JSON; +import static org.apache.archiva.redback.rest.api.Constants.DEFAULT_PAGE_LIMIT; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.equalTo; +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author Martin Stockhammer + */ +@ExtendWith( SpringExtension.class ) +@ContextConfiguration( + locations = {"classpath:/ldap-spring-test.xml"} ) +@TestInstance( TestInstance.Lifecycle.PER_CLASS ) +@Tag( "rest-native" ) +@TestMethodOrder( MethodOrderer.Random.class ) +@DisplayName( "Native REST tests for V2 RoleService" ) +public class NativeRoleServiceTest extends AbstractNativeRestServices +{ + @Override + protected String getServicePath( ) + { + return "/roles"; + } + + @BeforeAll + void setup( ) throws Exception + { + super.setupNative( ); + } + + @AfterAll + void destroy( ) throws Exception + { + super.shutdownNative( ); + } + + private String getUserServicePath() + { + return new StringBuilder( ) + .append( getContextRoot( ) ) + .append( getServiceBasePath( ) ) + .append( "/users" ).toString( ); + } + + + + @Test + void createTemplatedRole( ) + { + String token = getAdminToken( ); + try + { + Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .put( "template/archiva-repository-manager/repository01" ) + .then( ).statusCode( 201 ).extract( ).response( ); + assertNotNull( response ); + RoleInfo roleInfo = response.getBody( ).jsonPath( ).getObject( "", RoleInfo.class ); + assertNotNull( response.getHeader( "Location" ) ); + assertTrue( response.getHeader( "Location" ).endsWith( "/roles/" + roleInfo.getId( ) ) ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .put( "template/archiva-repository-manager/repository01" ) + .then( ).statusCode( 200 ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .head( "template/archiva-repository-manager/repository01" ) + .then( ).statusCode( 200 ); + // Repository observer is child template of repository-manager and will be created too + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .head( "template/archiva-repository-observer/repository01" ) + .then( ).statusCode( 200 ); + + } + finally + { + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .delete( "template/archiva-repository-manager/repository01" ) + .then( ).statusCode( 200 ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .delete( "template/archiva-repository-observer/repository01" ) + .then( ).statusCode( 200 ); + } + } + + @Test + void createTemplatedRoleWithNonexistentTemplate( ) + { + String token = getAdminToken( ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .put( "template/abcdefg/repository01" ) + .then( ).statusCode( 404 ); + } + + @Test + void deleteTemplatedRole( ) + { + String token = getAdminToken( ); + try + { + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .put( "template/archiva-repository-manager/repository05" ) + .then( ).statusCode( 201 ).extract( ).response( ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .delete( "template/archiva-repository-manager/repository01" ) + .then( ).statusCode( 404 ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .delete( "template/archiva-repository-manager/repository05" ) + .then( ).statusCode( 200 ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .delete( "template/archiva-repository-manager/repository05" ) + .then( ).statusCode( 404 ); + } finally + { + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .delete( "template/archiva-repository-observer/repository01" ) + .then( ).statusCode( 200 ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .delete( "template/archiva-repository-observer/repository05" ) + .then( ).statusCode( 200 ); + + } + } + + @Test + void checkTemplatedRole() { + String token = getAdminToken( ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .put( "template/archiva-repository-observer/repository01" ) + .then( ).statusCode( 201 ); + try { + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .head( "template/archiva-repository-observer/repository01" ) + .then( ).statusCode( 200 ); + + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .head( "archiva-repository-observer.repository01" ) + .then( ).statusCode( 200 ); + } finally + { + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .delete( "template/archiva-repository-observer/repository01" ) + .then( ).statusCode( 200 ); + } + + } + + + @Nested + @DisplayName( "Test Role queries" ) + @ContextConfiguration( + locations = {"classpath:/ldap-spring-test.xml"} ) + @TestInstance( TestInstance.Lifecycle.PER_CLASS ) + class TestRoleRetrieval + { + int roleInstances = 3; + String token; + + @BeforeAll + void initRoles( ) + { + this.token = getAdminToken( ); + for ( int i = 0; i < roleInstances; i++ ) + { + String suffix = String.format( "%03d", i ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .put( "template/archiva-repository-manager/repo" + suffix ) + .then( ).statusCode( 201 ).extract( ).response( ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .put( "template/archiva-repository-observer/repo" + suffix ) + .then( ).statusCode( anyOf( equalTo( 200 ), equalTo( 201 ) ) ).extract( ).response( ); + } + } + + @Test + void getMultipleRolesWithoutParams( ) + { + Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ).get( ).then( ).statusCode( 200 ).extract( ).response( ); + assertNotNull( response ); + List roleData = response.body( ).jsonPath( ).getList( "data", RoleInfo.class ); + assertNotNull( roleData ); + assertEquals( roleInstances * 2 + 9, roleData.size( ) ); + assertEquals( Integer.valueOf( 0 ), response.body( ).jsonPath( ).get( "pagination.offset" ) ); + assertEquals( Integer.valueOf( DEFAULT_PAGE_LIMIT ), response.body( ).jsonPath( ).get( "pagination.limit" ) ); + assertEquals( Integer.valueOf( roleInstances * 2 + 9 ), response.body( ).jsonPath( ).get( "pagination.total_count" ) ); + + } + + @Test + void getMultipleRolesWithPaging( ) + { + HashMap params = new HashMap<>( ); + params.put( "limit", Integer.toString( 10 ) ); + params.put( "offset", Integer.toString( 1 ) ); + Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ).params( params ).get( ).then( ).statusCode( 200 ).extract( ).response( ); + List userData = response.body( ).jsonPath( ).getList( "data", RoleInfo.class ); + assertNotNull( userData ); + response.getBody( ).jsonPath( ).prettyPrint( ); + assertEquals( 10, userData.size( ) ); + assertEquals( Integer.valueOf( 1 ), response.body( ).jsonPath( ).get( "pagination.offset" ) ); + assertEquals( Integer.valueOf( 10 ), response.body( ).jsonPath( ).get( "pagination.limit" ) ); + assertEquals( Integer.valueOf( roleInstances * 2 + 9 ), response.body( ).jsonPath( ).get( "pagination.total_count" ) ); + } + + @Test + void getMultipleUsersWithPagingOrderByIdAndResource( ) + { + HashMap params = new HashMap<>( ); + params.put( "limit", Integer.toString( 8 ) ); + params.put( "offset", Integer.toString( 5 ) ); + params.put( "orderBy", Arrays.asList( "id", "resource" ) ); + Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ).params( params ).get( ).then( ).statusCode( 200 ).extract( ).response( ); + List userData = response.body( ).jsonPath( ).getList( "data", RoleInfo.class ); + assertNotNull( userData ); + // admin user has toto@toto.org as email so is after aragorn + assertEquals( "repo002", userData.get( 0 ).getResource( ) ); + assertEquals( "repo000", userData.get( 1 ).getResource( ) ); + assertEquals( "archiva-repository-observer.repo000", userData.get( 1 ).getId( ) ); + assertEquals( "archiva-system-administrator", userData.get( 4 ).getId( ) ); + assertEquals( 8, userData.size( ) ); + assertEquals( Integer.valueOf( 5 ), response.body( ).jsonPath( ).get( "pagination.offset" ) ); + assertEquals( Integer.valueOf( 8 ), response.body( ).jsonPath( ).get( "pagination.limit" ) ); + assertEquals( Integer.valueOf( roleInstances * 2 + 9 ), response.body( ).jsonPath( ).get( "pagination.total_count" ) ); + } + + @Test + void getMultipleUsersWithPagingOrderByIdAndResourceReverse( ) + { + HashMap params = new HashMap<>( ); + params.put( "limit", Integer.toString( 7 ) ); + params.put( "offset", Integer.toString( 1 ) ); + params.put( "orderBy", Arrays.asList( "id", "resource" ) ); + params.put( "order", "desc" ); + Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ).params( params ).get( ).then( ).statusCode( 200 ).extract( ).response( ); + response.getBody( ).jsonPath( ).prettyPrint( ); + List userData = response.body( ).jsonPath( ).getList( "data", RoleInfo.class ); + assertNotNull( userData ); + // admin user has toto@toto.org as email so is after aragorn + assertEquals( "system-administrator", userData.get( 0 ).getId( ) ); + assertEquals( "registered-user", userData.get( 1 ).getId( ) ); + assertEquals( "guest", userData.get( 2 ).getId( ) ); + assertEquals( "archiva-repository-observer.repo002", userData.get( 5 ).getId( ) ); + assertEquals( 7, userData.size( ) ); + assertEquals( Integer.valueOf( 1 ), response.body( ).jsonPath( ).get( "pagination.offset" ) ); + assertEquals( Integer.valueOf( 7 ), response.body( ).jsonPath( ).get( "pagination.limit" ) ); + assertEquals( Integer.valueOf( roleInstances * 2 + 9 ), response.body( ).jsonPath( ).get( "pagination.total_count" ) ); + } + + @Test + void getMultipleRolesWithPagingAndQuery( ) + { + HashMap params = new HashMap<>( ); + params.put( "limit", Integer.toString( 10 ) ); + params.put( "offset", Integer.toString( 0 ) ); + params.put( "order", "asc" ); + params.put( "q", "system" ); + Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ).params( params ).get( ).then( ).statusCode( 200 ).extract( ).response( ); + List userData = response.body( ).jsonPath( ).getList( "data", RoleInfo.class ); + assertNotNull( userData ); + assertEquals( 2, userData.size( ) ); + assertEquals( Integer.valueOf( 0 ), response.body( ).jsonPath( ).get( "pagination.offset" ) ); + assertEquals( Integer.valueOf( 10 ), response.body( ).jsonPath( ).get( "pagination.limit" ) ); + assertEquals( Integer.valueOf( 2 ), response.body( ).jsonPath( ).get( "pagination.total_count" ) ); + + } + + + @AfterAll + void cleanupRoles( ) + { + for ( int i = 0; i < roleInstances; i++ ) + { + String suffix = String.format( "%03d", i ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ).delete( "template/archiva-repository-manager/repo" + suffix ).then( ).statusCode( 200 ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ).delete( "template/archiva-repository-observer/repo" + suffix ).then( ).statusCode( 200 ); + } + + } + } + + @Test + void getRole( ) + { + String token = getAdminToken( ); + Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ).get( "archiva-system-administrator" ).then( ).statusCode( 200 ).extract( ).response( ); + assertNotNull( response ); + RoleInfo roleInfo = response.getBody( ).jsonPath( ).getObject( "", RoleInfo.class ); + assertNotNull( roleInfo ); + assertEquals( "archiva-system-administrator", roleInfo.getId( ) ); + assertEquals( "Archiva System Administrator", roleInfo.getName( ) ); + } + + @Test + void getNonExistingRole( ) + { + String token = getAdminToken( ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ).get( "abcdefg" ).then( ).statusCode( 404 ); + } + + @Test + void checkRole() { + String token = getAdminToken( ); + Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ).head( "archiva-system-administrator" ).then( ).statusCode( 200 ).extract( ).response( ); + assertEquals(0,response.getBody( ).asByteArray().length); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ).head( "abcdefg" ).then( ).statusCode( 404 ); + + } + + @Test + void moveRole() { + String token = getAdminToken( ); + try + { + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .put( "template/archiva-repository-manager/repository01" ) + .then( ).statusCode( 201 ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ).head( "template/archiva-repository-observer/repository01" ).then( ).statusCode( 200 ); + + Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ).post( "template/archiva-repository-manager/repository01/moveto/repository02" ).then( ).statusCode( 201 ).extract( ).response( ); + RoleInfo role = response.getBody( ).jsonPath( ).getObject( "", RoleInfo.class ); + assertNotNull( role ); + assertEquals( "archiva-repository-manager.repository02", role.getId( ) ); + assertEquals( "repository02", role.getResource( ) ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ).head( "template/archiva-repository-manager/repository01" ).then( ).statusCode( 404 ); + // Child templates are copied and not moved + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ).head( "template/archiva-repository-observer/repository01" ).then( ).statusCode( 200 ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ).head( "template/archiva-repository-observer/repository02" ).then( ).statusCode( 200 ); + + } finally + { + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .delete( "template/archiva-repository-manager/repository02" ) + .then( ).statusCode( 200 ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .delete( "template/archiva-repository-observer/repository01" ) + .then( ).statusCode( 200 ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .delete( "template/archiva-repository-observer/repository02" ) + .then( ).statusCode( 200 ); + + } + + } + + @Test + void moveRoleToExistingDestination() { + String token = getAdminToken( ); + try + { + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .put( "template/archiva-repository-manager/repository01" ) + .then( ).statusCode( 201 ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .put( "template/archiva-repository-manager/repository02" ) + .then( ).statusCode( 201 ); + Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ).redirects( ).follow( false ) + .post( "template/archiva-repository-manager/repository01/moveto/repository02" ).then( ).statusCode( 303 ) + .extract( ).response( ); + System.out.println( response.getHeader( "Location" ) ); + assertTrue( response.getHeader( "Location" ).endsWith( "/roles/template/archiva-repository-manager/repository02" ) ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ).head( "template/archiva-repository-manager/repository01" ).then( ).statusCode( 200 ); + } finally + { + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .delete( "template/archiva-repository-manager/repository01" ) + .then( ).statusCode( 200 ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .delete( "template/archiva-repository-manager/repository02" ) + .then( ).statusCode( 200 ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .delete( "template/archiva-repository-observer/repository02" ) + .then( ).statusCode( 200 ); + + } + + } + + + @Test + void assignRole() { + String token = getAdminToken( ); + Map jsonAsMap = new HashMap<>( ); + jsonAsMap.put( "user_id", "aragorn"); + jsonAsMap.put( "email", "aragorn@lordoftherings.org" ); + jsonAsMap.put( "full_name", "Aragorn King of Gondor " ); + jsonAsMap.put( "password", "pAssw0rD" ); + + try + { + given( ).spec( getRequestSpec( token, getUserServicePath() ) ).contentType( JSON ) + .body( jsonAsMap ) + .when( ) + .post( ) + .then( ).statusCode( 201 ); + + Response response = given( ).spec( getRequestSpec( token, getUserServicePath() ) ).contentType( JSON ) + .when( ) + .get( "aragorn/roles" ) + .then( ).statusCode( 200 ).extract( ).response( ); + List roles = response.getBody( ).jsonPath( ).getList( "", RoleInfo.class ); + assertFalse( roles.stream( ).filter( role -> "system-administrator".equals( role.getId( ) ) ).findAny( ).isPresent( ) ); + given( ).spec( getRequestSpec( token ) ).contentType( JSON ) + .when( ) + .put( "system-administrator/assign/aragorn" ) + .prettyPeek() + .then( ).statusCode( 200 ); + response = given( ).spec( getRequestSpec( token, getUserServicePath() ) ).contentType( JSON ) + .when( ) + .get( "aragorn/roles" ) + .then( ).statusCode( 200 ).extract( ).response( ); + roles = response.getBody( ).jsonPath( ).getList( "", RoleInfo.class ); + assertTrue( roles.stream( ).filter( role -> "system-administrator".equals( role.getId( ) ) ).findAny( ).isPresent( ) ); + } finally + { + given( ).spec( getRequestSpec( token, getUserServicePath() ) ).contentType( JSON ) + .when( ) + .delete( "aragorn" ).getBody( ); + } + } + +} diff --git a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeUserServiceTest.java b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeUserServiceTest.java index e8f6cefd..4181d278 100644 --- a/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeUserServiceTest.java +++ b/redback-integrations/redback-rest/redback-rest-services/src/test/java/org/apache/archiva/redback/rest/services/v2/NativeUserServiceTest.java @@ -18,10 +18,13 @@ package org.apache.archiva.redback.rest.services.v2; * under the License. */ +import io.restassured.path.json.JsonPath; import io.restassured.response.Response; +import org.apache.archiva.redback.rest.api.model.v2.BaseRoleInfo; import org.apache.archiva.redback.rest.api.model.v2.Operation; import org.apache.archiva.redback.rest.api.model.v2.Permission; import org.apache.archiva.redback.rest.api.model.v2.RegistrationKey; +import org.apache.archiva.redback.rest.api.model.v2.RoleInfo; import org.apache.archiva.redback.rest.api.model.v2.UserInfo; import org.apache.archiva.redback.rest.api.model.v2.VerificationStatus; import org.apache.archiva.redback.rest.services.mock.EmailMessage; @@ -44,6 +47,7 @@ import java.util.Map; import static io.restassured.RestAssured.given; import static io.restassured.http.ContentType.JSON; +import static org.apache.archiva.redback.rest.api.Constants.DEFAULT_PAGE_LIMIT; import static org.junit.jupiter.api.Assertions.*; /** @@ -87,7 +91,7 @@ public class NativeUserServiceTest extends AbstractNativeRestServices assertNotNull( userData ); assertEquals( 2, userData.size( ) ); assertEquals( Integer.valueOf( 0 ), response.body( ).jsonPath( ).get( "pagination.offset" ) ); - assertEquals( Integer.valueOf( 1000 ), response.body( ).jsonPath( ).get( "pagination.limit" ) ); + assertEquals( Integer.valueOf( DEFAULT_PAGE_LIMIT ), response.body( ).jsonPath( ).get( "pagination.limit" ) ); assertEquals( Integer.valueOf( 2 ), response.body( ).jsonPath( ).get( "pagination.total_count" ) ); } @@ -134,7 +138,7 @@ public class NativeUserServiceTest extends AbstractNativeRestServices assertEquals( "admin", userData.get( 0 ).getUserId( ) ); assertEquals( userNum + 2, userData.size( ) ); assertEquals( Integer.valueOf( 0 ), response.body( ).jsonPath( ).get( "pagination.offset" ) ); - assertEquals( Integer.valueOf( 1000 ), response.body( ).jsonPath( ).get( "pagination.limit" ) ); + assertEquals( Integer.valueOf( DEFAULT_PAGE_LIMIT ), response.body( ).jsonPath( ).get( "pagination.limit" ) ); assertEquals( Integer.valueOf( userNum + 2 ), response.body( ).jsonPath( ).get( "pagination.total_count" ) ); } @@ -1508,4 +1512,128 @@ public class NativeUserServiceTest extends AbstractNativeRestServices .then( ).statusCode( 200 ); } } + + @Test + void getUserRoles() { + String adminToken = getAdminToken( ); + + Map userMap = new HashMap<>( ); + userMap.put( "user_id", "bilbo" ); + userMap.put( "email", "bilbo@lordoftherings.org" ); + userMap.put( "full_name", "Bilbo Beutlin" ); + userMap.put( "validated", true ); + userMap.put( "password", "pAssw0rD" ); + userMap.put( "confirm_password", "pAssw0rD" ); + given( ).spec( getRequestSpec( adminToken ) ).contentType( JSON ) + .body( userMap ) + .when( ) + .post( ) + .then( ).statusCode( 201 ); + + + try + { + Response response = given( ).spec( getRequestSpec( adminToken ) ).contentType( JSON ) + .when( ) + .get( "bilbo/roles" ) + .then( ).statusCode( 200 ).extract( ).response( ); + assertNotNull( response ); + JsonPath jsonPath = response.getBody( ).jsonPath( ); + List roleList = jsonPath.getList( "", RoleInfo.class ); + assertEquals( 1, roleList.size( ) ); + assertEquals( "registered-user", roleList.get( 0 ).getId( ) ); + + response = given( ).spec( getRequestSpec( adminToken ) ).contentType( JSON ) + .when( ) + .get( "admin/roles" ) + .then( ).statusCode( 200 ).extract( ).response( ); + assertNotNull( response ); + jsonPath = response.getBody( ).jsonPath( ); + roleList = jsonPath.getList( "", RoleInfo.class ); + jsonPath.prettyPrint( ); + assertEquals( 4, roleList.size( ) ); + assertTrue( roleList.stream( ).filter( role -> "system-administrator".equals( role.getId( ) ) ).findAny( ).isPresent( ) ); + assertTrue( roleList.stream( ).filter( role -> "archiva-global-repository-manager".equals( role.getId( ) ) ).findAny( ).isPresent( ) ); + assertTrue( roleList.stream( ).filter( role -> "archiva-global-repository-observer".equals( role.getId( ) ) ).findAny( ).isPresent( ) ); + assertTrue( roleList.stream( ).filter( role -> "user-administrator".equals( role.getId( ) ) ).findAny( ).isPresent( ) ); + + + } + finally + { + given( ).spec( getRequestSpec( adminToken ) ).contentType( JSON ) + .delete( "bilbo" ) + .then( ).statusCode( 200 ); + } + } + + @Test + void getRoleTree() { + String adminToken = getAdminToken( ); + + Map userMap = new HashMap<>( ); + userMap.put( "user_id", "bilbo" ); + userMap.put( "email", "bilbo@lordoftherings.org" ); + userMap.put( "full_name", "Bilbo Beutlin" ); + userMap.put( "validated", true ); + userMap.put( "password", "pAssw0rD" ); + userMap.put( "confirm_password", "pAssw0rD" ); + given( ).spec( getRequestSpec( adminToken ) ).contentType( JSON ) + .body( userMap ) + .when( ) + .post( ) + .then( ).statusCode( 201 ); + + + try + { + Response response = given( ).spec( getRequestSpec( adminToken ) ).contentType( JSON ) + .when( ) + .get( "bilbo/roletree" ) + .then( ).statusCode( 200 ).extract( ).response( ); + assertNotNull( response ); + JsonPath jsonPath = response.getBody( ).jsonPath( ); + assertTrue( jsonPath.getMap( "applications" ).containsKey( "System" ) ); + assertTrue( jsonPath.getMap( "applications" ).containsKey( "Archiva" ) ); + List roleList = jsonPath.getList( "root_roles", BaseRoleInfo.class ); + assertEquals( 3, roleList.size( ) ); + assertTrue( roleList.stream( ).filter( role -> role.getId( ).equals( "guest" ) ).findFirst( ).isPresent( ) ); + BaseRoleInfo registered = roleList.stream( ).filter( role -> role.getId( ).equals( "registered-user" ) ).findFirst( ).get( ); + assertTrue( registered.isAssigned( ) ); + BaseRoleInfo sysadmin = roleList.stream( ).filter( role -> role.getId( ).equals( "system-administrator" ) ).findFirst( ).get( ); + assertFalse( sysadmin.isAssigned( ) ); + assertFalse( sysadmin.isChild( ) ); + assertEquals( 2, sysadmin.getChildren( ).size( ) ); + assertTrue( sysadmin.getChildren( ).stream( ).filter( role -> role.getId( ).equals( "user-administrator" ) ).findFirst( ).isPresent( ) ); + + + response = given( ).spec( getRequestSpec( adminToken ) ).contentType( JSON ) + .when( ) + .get( "admin/roletree" ) + .then( ).statusCode( 200 ).extract( ).response( ); + assertNotNull( response ); + System.out.println( response.getBody( ).prettyPrint( ) ); + jsonPath = response.getBody( ).jsonPath( ); + assertTrue( jsonPath.getMap( "applications" ).containsKey( "System" ) ); + assertTrue( jsonPath.getMap( "applications" ).containsKey( "Archiva" ) ); + roleList = jsonPath.getList( "root_roles", BaseRoleInfo.class ); + assertEquals( 3, roleList.size( ) ); + assertTrue( roleList.stream( ).filter( role -> role.getId( ).equals( "guest" ) ).findFirst( ).isPresent( ) ); + registered = roleList.stream( ).filter( role -> role.getId( ).equals( "registered-user" ) ).findFirst( ).get( ); + assertFalse( registered.isAssigned( ) ); + sysadmin = roleList.stream( ).filter( role -> role.getId( ).equals( "system-administrator" ) ).findFirst( ).get( ); + assertTrue( sysadmin.isAssigned( ) ); + assertFalse( sysadmin.isChild( ) ); + assertEquals( 2, sysadmin.getChildren( ).size( ) ); + assertTrue( sysadmin.getChildren( ).stream( ).filter( role -> role.getId( ).equals( "user-administrator" ) ).findFirst( ).isPresent( ) ); + + + } + finally + { + given( ).spec( getRequestSpec( adminToken ) ).contentType( JSON ) + .delete( "bilbo" ) + .then( ).statusCode( 200 ); + } + } } diff --git a/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/AbstractRBACManager.java b/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/AbstractRBACManager.java index 8ace7e6c..bdecd29d 100644 --- a/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/AbstractRBACManager.java +++ b/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/AbstractRBACManager.java @@ -33,6 +33,7 @@ import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; +import java.util.stream.Stream; /** * AbstractRBACManager @@ -499,7 +500,7 @@ public abstract class AbstractRBACManager if ( role.hasChildRoles() ) { - Map childRoles = getChildRoles( role ); + Map childRoles = getChildRoleNames( role ); Iterator it = childRoles.values().iterator(); while ( it.hasNext() ) { @@ -736,11 +737,11 @@ public abstract class AbstractRBACManager throws RbacObjectInvalidException, RbacManagerException { saveRole( childRole ); - role.addChildRoleName( childRole.getName() ); + role.addChildRole( childRole ); } @Override - public Map getChildRoles( Role role ) + public Map getChildRoleNames( Role role ) throws RbacManagerException { Map childRoles = new HashMap(); @@ -797,7 +798,64 @@ public abstract class AbstractRBACManager } @Override - public Map getParentRoles( Role role ) + public Map getChildRoleIds( Role role ) + throws RbacManagerException + { + Map childRoles = new HashMap(); + + boolean childRoleNamesUpdated = false; + + Iterator it = role.getChildRoleIds().listIterator(); + + final List updatedChildRoleList = new ArrayList( role.getChildRoleIds().size() ); + + while ( it.hasNext() ) + { + String roleId = it.next(); + try + { + Role child = getRoleById( roleId ); + // archiva can change role manager but LDAP can be non writable so in such case + // some roles doesn't exists !! + if ( child != null ) + { + childRoles.put( child.getId(), child ); + updatedChildRoleList.add( roleId ); + } + else + { + log.warn( + "error searching role with name '{}' probably some issues when migrating your role manager", + roleId ); + } + } + catch ( RbacObjectNotFoundException e ) + { + // Found a bad roleName! - trigger new List save + //it.remove(); + childRoleNamesUpdated = true; + } + catch ( RbacManagerException e ) + { + if ( !( e.getCause() instanceof RbacObjectNotFoundException ) ) + { + throw e; + } + childRoleNamesUpdated = true; + } + } + + if ( childRoleNamesUpdated ) + { + role.setChildRoleIds( updatedChildRoleList ); + saveRole( role ); + } + + return childRoles; + } + + @Override + public Map getParentRoleNames( Role role ) throws RbacManagerException { Map parentRoles = new HashMap(); @@ -822,6 +880,23 @@ public abstract class AbstractRBACManager return parentRoles; } + @Override + public Map getParentRoleIds( final Role role ) throws RbacManagerException + { + return getAllRoles( ).stream( ).filter( r -> !r.getId( ).equals( role.getId( ) ) ) + .filter( r -> { + try + { + return getEffectiveRoles( r ).stream( ).map( Role::getId ).filter( cRoleId -> cRoleId.equals( role.getId( ) ) ).findAny( ).isPresent( ); + } + catch ( RbacManagerException e ) + { + return false; + } + } + ).distinct().collect( Collectors.toMap( Role::getId, Function.identity( ) ) ); + } + @Override public Set getEffectiveRoles( Role role ) throws RbacObjectNotFoundException, RbacManagerException diff --git a/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/AbstractRole.java b/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/AbstractRole.java index a90f66a3..5736f5b2 100644 --- a/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/AbstractRole.java +++ b/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/AbstractRole.java @@ -29,7 +29,7 @@ public abstract class AbstractRole @Override public boolean hasChildRoles() { - return ( getChildRoleNames() != null ) && !getChildRoleNames().isEmpty(); + return ( getChildRoleIds() != null ) && !getChildRoleIds().isEmpty(); } /** @@ -57,4 +57,11 @@ public abstract class AbstractRole return result; } + + @Override + public void addChildRole( Role child ) + { + addChildRoleName( child.getName() ); + addChildRoleId( child.getId() ); + } } diff --git a/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/RBACManager.java b/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/RBACManager.java index 11b56dba..16ac43d8 100644 --- a/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/RBACManager.java +++ b/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/RBACManager.java @@ -155,21 +155,39 @@ public interface RBACManager throws RbacObjectInvalidException, RbacManagerException; /** - * Returns all the child roles of a given role. + * Returns all the child roles of a given role as (name, role) pairs. * @param role the parent role * @return the list of child roles * @throws RbacManagerException if the access to the backend datastore failed */ - Map getChildRoles( Role role ) + Map getChildRoleNames( Role role ) throws RbacManagerException; /** - * Returns all the parent roles of a given role. + * Returns all the child roles of a given role as (role id, role) pairs. + * @param role the parent role + * @return the map of child roles as (role id, role) pairs + * @throws RbacManagerException if the access to the backend datastore failed + */ + Map getChildRoleIds( Role role ) + throws RbacManagerException; + + /** + * Returns all the parent roles of a given role as map of (name, role) elements. * @param role the role to check for parent roles * @return the list of parent roles that have role als child * @throws RbacManagerException if the access to the backend datastore failed */ - Map getParentRoles( Role role ) + Map getParentRoleNames( Role role ) + throws RbacManagerException; + + /** + * Returns all the parent roles of a given role as map of (id, role) elements. + * @param role the role to check for parents roles + * @return a map of (role id, role) pairs that have role as child + * @throws RbacManagerException if the access to the backend datastore failed + */ + Map getParentRoleIds( Role role ) throws RbacManagerException; /** diff --git a/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/Role.java b/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/Role.java index 407be446..f389d82e 100644 --- a/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/Role.java +++ b/redback-rbac/redback-rbac-model/src/main/java/org/apache/archiva/redback/rbac/Role.java @@ -58,6 +58,24 @@ public interface Role */ List getChildRoleNames(); + /** + * Adds a child role and sets the list of child names and child ids. + * @param child the child role + */ + void addChildRole( Role child ); + + /** + * Adds a child role id + * @param id the id + */ + void addChildRoleId( String id ); + + /** + * Returns the child role ids + * @return the list of child role ids + */ + List getChildRoleIds(); + /** * Convenience method to see if Role has Child Roles. * @@ -114,6 +132,12 @@ public interface Role */ void setChildRoleNames( List names ); + /** + * Sets the list of child role ids + * @param ids + */ + void setChildRoleIds( List ids ); + /** * Set the Description * @@ -220,4 +244,6 @@ public interface Role * @param resource the resource identifier. Must not be null. */ void setResource( String resource ); + + } \ No newline at end of file diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-cached/src/main/java/org/apache/archiva/redback/rbac/cached/CachedRbacManager.java b/redback-rbac/redback-rbac-providers/redback-rbac-cached/src/main/java/org/apache/archiva/redback/rbac/cached/CachedRbacManager.java index 8ffc360f..7fbbcddf 100644 --- a/redback-rbac/redback-rbac-providers/redback-rbac-cached/src/main/java/org/apache/archiva/redback/rbac/cached/CachedRbacManager.java +++ b/redback-rbac/redback-rbac-providers/redback-rbac-cached/src/main/java/org/apache/archiva/redback/rbac/cached/CachedRbacManager.java @@ -291,19 +291,33 @@ public class CachedRbacManager } @Override - public Map getChildRoles( Role role ) + public Map getChildRoleNames( Role role ) throws RbacManagerException { log.debug( "NOT CACHED - .getChildRoles(Role)" ); - return this.rbacImpl.getChildRoles( role ); + return this.rbacImpl.getChildRoleNames( role ); } @Override - public Map getParentRoles( Role role ) + public Map getChildRoleIds( Role role ) throws RbacManagerException + { + log.debug( "NOT CACHED - .getChildRoles(Role)" ); + return this.rbacImpl.getChildRoleIds( role ); + } + + @Override + public Map getParentRoleNames( Role role ) throws RbacManagerException { log.debug( "NOT CACHED - .getParentRoles(Role)" ); - return this.rbacImpl.getParentRoles( role ); + return this.rbacImpl.getParentRoleNames( role ); + } + + @Override + public Map getParentRoleIds( Role role ) throws RbacManagerException + { + log.debug( "NOT CACHED - .getParentRoles(Role)" ); + return this.rbacImpl.getParentRoleIds( role ); } @Override diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/JpaRbacManager.java b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/JpaRbacManager.java index 0c7a9da1..8bd32128 100644 --- a/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/JpaRbacManager.java +++ b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/JpaRbacManager.java @@ -35,7 +35,7 @@ import org.apache.archiva.redback.rbac.jpa.model.JpaPermission; import org.apache.archiva.redback.rbac.jpa.model.JpaResource; import org.apache.archiva.redback.rbac.jpa.model.JpaRole; import org.apache.archiva.redback.rbac.jpa.model.JpaUserAssignment; -import org.apache.commons.codec.digest.DigestUtils; +import org.apache.archiva.redback.rbac.jpa.model.RoleId; import org.springframework.stereotype.Service; import javax.persistence.EntityManager; @@ -44,9 +44,6 @@ import javax.persistence.PersistenceContext; import javax.persistence.Query; import javax.persistence.TypedQuery; import javax.transaction.Transactional; -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -138,8 +135,15 @@ public class JpaRbacManager extends AbstractRBACManager { @Transactional @Override - public Map getChildRoles(Role role) throws RbacManagerException { - return super.getChildRoles(role); + public Map getChildRoleNames( Role role) throws RbacManagerException { + return super.getChildRoleNames(role); + } + + @Transactional + @Override + public Map getChildRoleIds( Role role ) throws RbacManagerException + { + return super.getChildRoleIds( role ); } @Transactional @@ -219,7 +223,7 @@ public class JpaRbacManager extends AbstractRBACManager { throw new RbacPermanentException( "Unable to delete permanent role [" + role.getName() + "]" ); } final EntityManager em = getEm(); - JpaRole myRole = em.find(JpaRole.class, role.getName()); + JpaRole myRole = em.find(JpaRole.class, new RoleId( role.getId(), role.getName())); if (myRole == null) { throw new RbacObjectNotFoundException("Role not found "+role.getName()); } diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaRole.java b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaRole.java index d21eb6a5..7b775aea 100644 --- a/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaRole.java +++ b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/JpaRole.java @@ -21,6 +21,7 @@ package org.apache.archiva.redback.rbac.jpa.model; import org.apache.archiva.redback.rbac.AbstractRole; import org.apache.archiva.redback.rbac.Permission; +import org.apache.archiva.redback.rbac.Role; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.annotation.Order; @@ -37,15 +38,17 @@ import java.util.List; @Table( name="SECURITY_ROLES" ) +@IdClass( RoleId.class ) public class JpaRole extends AbstractRole implements Serializable { private static final Logger log = LoggerFactory.getLogger( JpaRole.class ); private static final long serialVersionUID = 4564608138465995665L; @Id - @Column(name="NAME") + @Column(name="NAME", unique = true) private String name; - @Column(name="ID", unique = true) + @Id + @Column( name = "ID", unique = true ) private String id; @Column(name="DESCRIPTION") private String description; @@ -75,6 +78,17 @@ public class JpaRole extends AbstractRole implements Serializable { ) List childRoleNames = new ArrayList(); + @ElementCollection(fetch = FetchType.EAGER) + @OrderColumn(name="INTEGER_IDX",nullable = false) + @Column(name="CHILD_IDS") + @CollectionTable( + name="SECURITY_ROLE_CHILDROLE_ID_MAP", + joinColumns = { + @JoinColumn(name="ID_OID",referencedColumnName = "ID", nullable = false) + } + ) + List childRoleIds = new ArrayList(); + @Column(name="TEMPLATE_INSTANCE",nullable = false) private Boolean templateInstance = false; @@ -101,11 +115,23 @@ public class JpaRole extends AbstractRole implements Serializable { this.childRoleNames.add(name); } + @Override + public void addChildRoleId( String id ) + { + this.childRoleIds.add( id ); + } + @Override public List getChildRoleNames() { return childRoleNames; } + @Override + public List getChildRoleIds( ) + { + return childRoleIds; + } + @Override public String getDescription() { return description; @@ -142,6 +168,13 @@ public class JpaRole extends AbstractRole implements Serializable { this.childRoleNames.addAll(names); } + @Override + public void setChildRoleIds( List childRoleIds ) + { + this.childRoleIds.clear(); + this.childRoleIds.addAll( childRoleIds ); + } + @Override public void setDescription(String description) { this.description=description; @@ -245,12 +278,15 @@ public class JpaRole extends AbstractRole implements Serializable { JpaRole jpaRole = (JpaRole) o; - return name.equals( jpaRole.name ); + if ( !name.equals( jpaRole.name ) ) return false; + return id.equals( jpaRole.id ); } @Override public int hashCode( ) { - return name.hashCode( ); + int result = name.hashCode( ); + result = 31 * result + id.hashCode( ); + return result; } } diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/RoleId.java b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/RoleId.java new file mode 100644 index 00000000..f63d77f0 --- /dev/null +++ b/redback-rbac/redback-rbac-providers/redback-rbac-jpa/src/main/java/org/apache/archiva/redback/rbac/jpa/model/RoleId.java @@ -0,0 +1,59 @@ +package org.apache.archiva.redback.rbac.jpa.model;/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + */ + +import java.io.Serializable; + +/** + * @author Martin Stockhammer + */ +public class RoleId implements Serializable +{ + private static final long serialVersionUID = -3358026083136193536L; + private String id; + private String name; + + public RoleId( ) + { + } + + public RoleId( String id, String name ) + { + this.id = id; + this.name = name; + } + + @Override + public boolean equals( Object o ) + { + if ( this == o ) return true; + if ( o == null || getClass( ) != o.getClass( ) ) return false; + + RoleId roleId = (RoleId) o; + + if ( !id.equals( roleId.id ) ) return false; + return name.equals( roleId.name ); + } + + @Override + public int hashCode( ) + { + int result = id.hashCode( ); + result = 31 * result + name.hashCode( ); + return result; + } +} diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-ldap/src/main/java/org/apache/archiva/redback/rbac/ldap/LdapRbacManager.java b/redback-rbac/redback-rbac-providers/redback-rbac-ldap/src/main/java/org/apache/archiva/redback/rbac/ldap/LdapRbacManager.java index df05dcf2..c5d9e8a8 100644 --- a/redback-rbac/redback-rbac-providers/redback-rbac-ldap/src/main/java/org/apache/archiva/redback/rbac/ldap/LdapRbacManager.java +++ b/redback-rbac/redback-rbac-providers/redback-rbac-ldap/src/main/java/org/apache/archiva/redback/rbac/ldap/LdapRbacManager.java @@ -60,10 +60,8 @@ import javax.naming.directory.DirContext; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -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 java.util.stream.Stream; @@ -474,17 +472,29 @@ public class LdapRbacManager } @Override - public Map getChildRoles( Role role ) + public Map getChildRoleNames( Role role ) throws RbacManagerException { - return this.rbacImpl.getChildRoles( role ); + return this.rbacImpl.getChildRoleNames( role ); } @Override - public Map getParentRoles( Role role ) + public Map getChildRoleIds( Role role ) throws RbacManagerException + { + return this.rbacImpl.getChildRoleIds( role ); + } + + @Override + public Map getParentRoleNames( Role role ) throws RbacManagerException { - return this.rbacImpl.getParentRoles( role ); + return this.rbacImpl.getParentRoleNames( role ); + } + + @Override + public Map getParentRoleIds( Role role ) throws RbacManagerException + { + return this.rbacImpl.getParentRoleIds( role ); } // @@ -1241,9 +1251,10 @@ public class LdapRbacManager private boolean isTemplateInstance=false; private String resource=""; - private List permissions = new ArrayList(); + private List permissions = new ArrayList<>(); - private List childRoleNames = new ArrayList(); + private List childRoleNames = new ArrayList<>(); + private List childRoleIds = new ArrayList<>( ); private RoleImpl( String name ) { @@ -1280,6 +1291,18 @@ public class LdapRbacManager return this.childRoleNames; } + @Override + public void addChildRoleId( String id ) + { + this.childRoleIds.add( id ); + } + + @Override + public List getChildRoleIds( ) + { + return this.childRoleIds; + } + @Override public String getDescription() { @@ -1322,6 +1345,12 @@ public class LdapRbacManager this.childRoleNames = names; } + @Override + public void setChildRoleIds( List ids ) + { + + } + @Override public void setDescription( String description ) { diff --git a/redback-rbac/redback-rbac-providers/redback-rbac-memory/src/main/java/org/apache/archiva/redback/rbac/memory/MemoryRole.java b/redback-rbac/redback-rbac-providers/redback-rbac-memory/src/main/java/org/apache/archiva/redback/rbac/memory/MemoryRole.java index c243af86..1eed3a07 100644 --- a/redback-rbac/redback-rbac-providers/redback-rbac-memory/src/main/java/org/apache/archiva/redback/rbac/memory/MemoryRole.java +++ b/redback-rbac/redback-rbac-providers/redback-rbac-memory/src/main/java/org/apache/archiva/redback/rbac/memory/MemoryRole.java @@ -61,6 +61,8 @@ public class MemoryRole */ private List childRoleNames = new ArrayList<>( 0 ); + private List childRoleIds = new ArrayList<>( 0 ); + /** * Field permissions */ @@ -110,6 +112,20 @@ public class MemoryRole return this.childRoleNames; } + + + @Override + public void addChildRoleId( String id ) + { + this.childRoleIds.add( id ); + } + + @Override + public List getChildRoleIds( ) + { + return this.childRoleIds; + } + @Override public String getDescription() { @@ -198,6 +214,12 @@ public class MemoryRole } } + @Override + public void setChildRoleIds( List ids ) + { + + } + @Override public boolean isPermanent() { diff --git a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/DefaultRoleManager.java b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/DefaultRoleManager.java index 9d68db62..9fbf5c9a 100644 --- a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/DefaultRoleManager.java +++ b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/DefaultRoleManager.java @@ -21,6 +21,7 @@ package org.apache.archiva.redback.role; import org.apache.archiva.redback.rbac.RBACManager; import org.apache.archiva.redback.rbac.RbacManagerException; +import org.apache.archiva.redback.rbac.RbacObjectNotFoundException; import org.apache.archiva.redback.rbac.Role; import org.apache.archiva.redback.rbac.UserAssignment; import org.apache.archiva.redback.role.model.ModelApplication; @@ -184,10 +185,10 @@ public class DefaultRoleManager * resolving the ${resource} expression */ @Override - public void createTemplatedRole( String templateId, String resource ) + public String createTemplatedRole( String templateId, String resource ) throws RoleManagerException { - templateProcessor.create( blessedModel, templateId, resource ); + return templateProcessor.create( blessedModel, templateId, resource ); } /** @@ -198,13 +199,10 @@ public class DefaultRoleManager public void removeTemplatedRole( String templateId, String resource ) throws RoleManagerException { - ModelTemplate template = RoleModelUtils.getModelTemplate( blessedModel, templateId ); - - String roleName = template.getNamePrefix() + template.getDelimiter() + resource; - + String roleId = templateProcessor.getRoleId( templateId, resource ); try { - Role role = rbacManager.getRole( roleName ); + Role role = rbacManager.getRoleById( roleId ); for ( UserAssignment assignment : rbacManager.getUserAssignmentsForRoles( Arrays.asList( role.getName() ) ) ) @@ -213,10 +211,12 @@ public class DefaultRoleManager rbacManager.saveUserAssignment( assignment ); } + } catch ( RbacObjectNotFoundException e) { + throw new RoleNotFoundException( e.getMessage( ), e ); } catch ( RbacManagerException e ) { - throw new RoleManagerException( "unable to remove role", e ); + throw new RoleManagerException( "Unable to remove role", e ); } templateProcessor.remove( blessedModel, templateId, resource ); @@ -229,11 +229,11 @@ public class DefaultRoleManager * because of the use of the name as an identifier */ @Override - public void moveTemplatedRole( String templateId, String oldResource, String newResource ) + public String moveTemplatedRole( String templateId, String oldResource, String newResource ) throws RoleManagerException { // make the new role - templateProcessor.create( blessedModel, templateId, newResource ); + String roleId = templateProcessor.create( blessedModel, templateId, newResource ); ModelTemplate template = RoleModelUtils.getModelTemplate( blessedModel, templateId ); @@ -259,6 +259,7 @@ public class DefaultRoleManager } templateProcessor.remove( blessedModel, templateId, oldResource ); + return roleId; } @Override @@ -269,7 +270,7 @@ public class DefaultRoleManager if ( modelRole == null ) { - throw new RoleManagerException( "Unable to assign role: " + roleId + " does not exist." ); + throw new RoleNotFoundException( "Unable to assign role: " + roleId + " does not exist." ); } try diff --git a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleExistsException.java b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleExistsException.java new file mode 100644 index 00000000..0403cb3e --- /dev/null +++ b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleExistsException.java @@ -0,0 +1,33 @@ +package org.apache.archiva.redback.role;/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + */ + +/** + * @author Martin Stockhammer + */ +public class RoleExistsException extends RoleManagerException +{ + public RoleExistsException( String string ) + { + super( string ); + } + + public RoleExistsException( String string, Throwable throwable ) + { + super( string, throwable ); + } +} diff --git a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleManager.java b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleManager.java index 7c727e8c..7448a8f8 100644 --- a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleManager.java +++ b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleManager.java @@ -53,7 +53,7 @@ public interface RoleManager * @param resource * @throws RoleManagerException */ - void createTemplatedRole( String templateId, String resource ) + String createTemplatedRole( String templateId, String resource ) throws RoleManagerException; /** @@ -78,7 +78,7 @@ public interface RoleManager * @param newResource the new resource name * @throws RoleManagerException */ - void moveTemplatedRole( String templateId, String oldResource, String newResource ) + String moveTemplatedRole( String templateId, String oldResource, String newResource ) throws RoleManagerException; diff --git a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleNotFoundException.java b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleNotFoundException.java new file mode 100644 index 00000000..e29c14a8 --- /dev/null +++ b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/RoleNotFoundException.java @@ -0,0 +1,33 @@ +package org.apache.archiva.redback.role;/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * 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. + */ + +/** + * @author Martin Stockhammer + */ +public class RoleNotFoundException extends RoleManagerException +{ + public RoleNotFoundException( String string ) + { + super( string ); + } + + public RoleNotFoundException( String string, Throwable throwable ) + { + super( string, throwable ); + } +} diff --git a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/processor/DefaultRoleModelProcessor.java b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/processor/DefaultRoleModelProcessor.java index e0b4b65c..b39c0c7d 100644 --- a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/processor/DefaultRoleModelProcessor.java +++ b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/processor/DefaultRoleModelProcessor.java @@ -216,6 +216,7 @@ public class DefaultRoleModelProcessor { ModelRole childRoleProfile = RoleModelUtils.getModelRole( model, childRoleId ); role.addChildRoleName( childRoleProfile.getName() ); + role.addChildRoleId( childRoleProfile.getId() ); } } @@ -229,7 +230,7 @@ public class DefaultRoleModelProcessor { ModelRole parentModelRole = RoleModelUtils.getModelRole( model, parentRoleId ); Role parentRole = rbacManager.getRole( parentModelRole.getName() ); - parentRole.addChildRoleName( role.getName() ); + parentRole.addChildRole( role ); rbacManager.saveRole( parentRole ); allRoleNames.add( parentRole.getName() ); } diff --git a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/template/DefaultRoleTemplateProcessor.java b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/template/DefaultRoleTemplateProcessor.java index 3c433334..f7533c19 100644 --- a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/template/DefaultRoleTemplateProcessor.java +++ b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/template/DefaultRoleTemplateProcessor.java @@ -25,7 +25,9 @@ import org.apache.archiva.redback.rbac.RbacManagerException; import org.apache.archiva.redback.rbac.Resource; import org.apache.archiva.redback.rbac.Role; import org.apache.archiva.redback.rbac.RBACManager; +import org.apache.archiva.redback.role.RoleExistsException; import org.apache.archiva.redback.role.RoleManagerException; +import org.apache.archiva.redback.role.RoleNotFoundException; import org.apache.archiva.redback.role.model.ModelApplication; import org.apache.archiva.redback.role.model.ModelOperation; import org.apache.archiva.redback.role.model.ModelPermission; @@ -60,8 +62,9 @@ public class DefaultRoleTemplateProcessor @Named(value = "rbacManager#default") private RBACManager rbacManager; + @Override @SuppressWarnings("unchecked") - public void create( RedbackRoleModel model, String templateId, String resource ) + public String create( final RedbackRoleModel model, final String templateId, final String resource ) throws RoleManagerException { for ( ModelApplication application : model.getApplications() ) @@ -74,16 +77,16 @@ public class DefaultRoleTemplateProcessor processResource( template, resource ); // templates are roles that have yet to be paired with a resource for creation - processTemplate( model, template, resource ); + return processTemplate( model, template, resource ); - return; } } } - throw new RoleManagerException( "unknown template '" + templateId + "'" ); + throw new RoleNotFoundException( "unknown template '" + templateId + "'" ); } + @Override @SuppressWarnings("unchecked") public void remove( RedbackRoleModel model, String templateId, String resource ) throws RoleManagerException @@ -106,11 +109,11 @@ public class DefaultRoleTemplateProcessor private void removeTemplatedRole( RedbackRoleModel model, ModelTemplate template, String resource ) throws RoleManagerException { - String roleName = template.getNamePrefix() + template.getDelimiter() + resource; + String roleId = getRoleId( template.getId( ), resource ); try { - Role role = rbacManager.getRole( roleName ); + Role role = rbacManager.getRoleById( roleId ); if ( !role.isPermanent() ) { @@ -142,12 +145,12 @@ public class DefaultRoleTemplateProcessor } else { - throw new RoleManagerException( "unable to remove role, it is flagged permanent" ); + throw new RoleManagerException( "Unable to remove role, it is flagged permanent" ); } } catch ( RbacManagerException e ) { - throw new RoleManagerException( "unable to remove templated role: " + roleName, e ); + throw new RoleManagerException( "Unable to remove templated role: " + roleId, e ); } //catch ( RoleTemplateProcessorException e ) //{ @@ -173,12 +176,17 @@ public class DefaultRoleTemplateProcessor } } + @Override + public String getRoleId( String templateId, String resource) { + return templateId + "." + resource; + } + @SuppressWarnings("unchecked") - private void processTemplate( RedbackRoleModel model, ModelTemplate template, String resource ) + private String processTemplate( RedbackRoleModel model, ModelTemplate template, String resource ) throws RoleManagerException { final String templateName = template.getNamePrefix() + template.getDelimiter() + resource; - final String roleId = template.getId( ) + "." + resource; + final String roleId = getRoleId( template.getId( ), resource ); List permissions = processPermissions( model, template, resource ); @@ -190,7 +198,7 @@ public class DefaultRoleTemplateProcessor } catch ( RbacManagerException e ) { - throw new RoleManagerException( e.getMessage(), e ); + throw new RoleExistsException( e.getMessage(), e ); } if ( !roleExists ) @@ -221,6 +229,7 @@ public class DefaultRoleTemplateProcessor { ModelRole childRoleProfile = RoleModelUtils.getModelRole( model, childRoleId ); role.addChildRoleName( childRoleProfile.getName() ); + role.addChildRoleId( childRoleProfile.getId() ); } } @@ -246,12 +255,14 @@ public class DefaultRoleTemplateProcessor if ( rbacManager.roleExists( childRoleName ) ) { role.addChildRoleName( childRoleName ); + role.addChildRoleId( getRoleId( childTemplateId, resource ) ); } else { processTemplate( model, childModelTemplate, resource ); role.addChildRoleName( childRoleName ); + role.addChildRoleId( getRoleId( childTemplateId, resource ) ); } } } @@ -270,7 +281,7 @@ public class DefaultRoleTemplateProcessor { ModelRole parentModelRole = RoleModelUtils.getModelRole( model, parentRoleId ); Role parentRole = rbacManager.getRole( parentModelRole.getName() ); - parentRole.addChildRoleName( role.getName() ); + parentRole.addChildRole( role ); rbacManager.saveRole( parentRole ); } } @@ -298,7 +309,7 @@ public class DefaultRoleTemplateProcessor { Role parentRole = rbacManager.getRole( parentRoleName ); - parentRole.addChildRoleName( role.getName() ); + parentRole.addChildRole( role ); rbacManager.saveRole( parentRole ); } else @@ -307,7 +318,7 @@ public class DefaultRoleTemplateProcessor Role parentRole = rbacManager.getRole( parentRoleName ); - parentRole.addChildRoleName( role.getName() ); + parentRole.addChildRole( role ); rbacManager.saveRole( parentRole ); } } @@ -358,6 +369,7 @@ public class DefaultRoleTemplateProcessor throw new RoleManagerException( "error updating role '" + templateName + "'", e ); } } + return roleId; } @SuppressWarnings("unchecked") diff --git a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/template/RoleTemplateProcessor.java b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/template/RoleTemplateProcessor.java index 7f8e07d8..e237a04a 100644 --- a/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/template/RoleTemplateProcessor.java +++ b/redback-rbac/redback-rbac-role-manager/src/main/java/org/apache/archiva/redback/role/template/RoleTemplateProcessor.java @@ -30,9 +30,33 @@ import org.apache.archiva.redback.role.model.RedbackRoleModel; public interface RoleTemplateProcessor { - void create( RedbackRoleModel model, String templateId, String resource ) + /** + * Creates a role instance from a template for the given resource and returns the id of the new role. + * @param model the model + * @param templateId the template identifier + * @param resource the resource to which the role is applied + * @return the id of the role + * @throws RoleManagerException if the access to the backend datastore failed + */ + String create( RedbackRoleModel model, String templateId, String resource ) throws RoleManagerException; + /** + * Removes the role instance that belongs to the template from the datastore + * @param model the model + * @param templateId the template identifier + * @param resource the resource to which the role is applied + * @throws RoleManagerException if the access to the backend datastore failed + */ void remove( RedbackRoleModel model, String templateId, String resource ) throws RoleManagerException; + + + /** + * Returns the role id that identifies the role that is a instance of the given template for the given resource. + * @param templateId the template identifier + * @param resource the resource + * @return the role identifier + */ + String getRoleId( String templateId, String resource ); } diff --git a/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/AbstractRbacManagerPerformanceTestCase.java b/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/AbstractRbacManagerPerformanceTestCase.java index 11eacdd1..7d1512f2 100644 --- a/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/AbstractRbacManagerPerformanceTestCase.java +++ b/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/AbstractRbacManagerPerformanceTestCase.java @@ -169,6 +169,7 @@ public class AbstractRbacManagerPerformanceTestCase Role devRole = getDeveloperRole(); Role devPlusRole = getSuperDeveloperRole(); devPlusRole.setChildRoleNames( Collections.singletonList( devRole.getName() ) ); + devPlusRole.setChildRoleIds( Collections.singletonList( devRole.getId() ) ); devRole = manager.saveRole( devRole ); devPlusRole = manager.saveRole( devPlusRole ); @@ -197,6 +198,7 @@ public class AbstractRbacManagerPerformanceTestCase username = "janet"; devPlusRole.setChildRoleNames( Collections.singletonList( devRole.getName() ) ); + devPlusRole.setChildRoleIds( Collections.singletonList( devRole.getId() ) ); devRole = manager.saveRole( devRole ); manager.saveRole( devPlusRole ); diff --git a/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/AbstractRbacManagerTestCase.java b/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/AbstractRbacManagerTestCase.java index 5ec4cc5c..bca655e9 100644 --- a/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/AbstractRbacManagerTestCase.java +++ b/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/AbstractRbacManagerTestCase.java @@ -91,7 +91,7 @@ public abstract class AbstractRbacManagerTestCase private Role getAdminRole() throws RbacManagerException { - Role role = rbacManager.createRole( "ADMIN" ); + Role role = rbacManager.createRole( "admin", "ADMIN" ); role.setAssignable( false ); Permission perm = rbacManager.createPermission( "EDIT_ANY_USER", "EDIT", "User:*" ); @@ -375,6 +375,7 @@ public abstract class AbstractRbacManagerTestCase manager.saveRole( projectRole ); develRole.addChildRoleName( projectRoleName ); + develRole.addChildRoleId( projectRole.getId() ); manager.saveRole( develRole ); @@ -428,7 +429,7 @@ public abstract class AbstractRbacManagerTestCase Role adminRole = getAdminRole(); - adminRole.addChildRoleName( developerRole.getName() ); + adminRole.addChildRole( developerRole ); adminRole = manager.saveRole( adminRole ); @@ -631,6 +632,7 @@ public abstract class AbstractRbacManagerTestCase Role devRole = getDeveloperRole(); Role devPlusRole = getSuperDeveloperRole(); devPlusRole.setChildRoleNames( Collections.singletonList( devRole.getName() ) ); + devPlusRole.setChildRoleIds( Collections.singletonList( devRole.getId() ) ); manager.saveRole( devRole ); manager.saveRole( devPlusRole ); diff --git a/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/utils/RBACDefaults.java b/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/utils/RBACDefaults.java index 16c85afc..a4514850 100644 --- a/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/utils/RBACDefaults.java +++ b/redback-rbac/redback-rbac-tests/src/main/java/org/apache/archiva/redback/tests/utils/RBACDefaults.java @@ -159,6 +159,7 @@ public class RBACDefaults { Role admin = manager.createRole( "System Administrator" ); admin.addChildRoleName( "User Administrator" ); + admin.addChildRoleId( "user-administrator" ); admin.addPermission( manager.getPermission( "Edit Configuration" ) ); admin.addPermission( manager.getPermission( "Run Indexer" ) ); admin.addPermission( manager.getPermission( "Add Repository" ) ); @@ -171,6 +172,7 @@ public class RBACDefaults { Role developer = manager.createRole( "Trusted Developer" ); developer.addChildRoleName( "System Administrator" ); + developer.addChildRoleId( "system-administrator" ); developer.addPermission( manager.getPermission( "Run Indexer" ) ); developer.setAssignable( true ); manager.saveRole( developer ); @@ -180,6 +182,7 @@ public class RBACDefaults { Role developer = manager.createRole( "Developer" ); developer.addChildRoleName( "Trusted Developer" ); + developer.addChildRoleId( "trusted-developer" ); developer.addPermission( manager.getPermission( "Run Indexer" ) ); developer.setAssignable( true ); manager.saveRole( developer );