From 3fef27e79e26ec5bf365f877e5de2b1e39949b24 Mon Sep 17 00:00:00 2001 From: "adel.ka" Date: Fri, 29 Aug 2025 17:20:12 +0300 Subject: [PATCH] SUPPORT-9363: permissions --- .../service/AccountGridLoadService.java | 8 ++-- .../component/service/AuditFormDaoImpl.java | 2 +- .../service/RecruitmentGridService.java | 8 ++-- .../component/service/TreeItemService.java | 9 ++-- .../security/config/SecurityConfig.java | 12 +---- .../security/context/SecurityContext.java | 7 ++- .../security/context/SecurityContextImpl.java | 11 ++++- .../controller/AuthorizationController.java | 8 ++-- .../security/dto/SessionDto.java | 2 +- .../filter/JwtAuthenticationFilter.java | 12 +++-- .../security/model/{jwt => }/UserSession.java | 14 +++--- .../security/model/jwt/UserClaims.java | 47 +++++++++++++++++++ .../JwtTokenAuthentication.java | 4 +- .../jwt/authentication/JwtTokenDummy.java | 8 +++- .../model/role/ErvuPermissionAuthority.java | 16 +++++++ .../model/role/ErvuRoleAuthority.java | 17 ------- .../ErvuJwtAuthenticationProvider.java | 34 +++++++++++++- .../security/service/ErvuJwtTokenService.java | 38 +++------------ .../security/service/JwtTokenService.java | 5 +- .../service/UserApplicationListService.java | 2 +- .../account_applications/ErvuCheckUserRole.ts | 6 +-- frontend/src/ts/mfe-app-tools.ts | 8 +++- .../permission.interceptor.service.ts | 28 +++++++++++ .../app/provider/permission.provider.ts | 5 ++ .../app/service/authorization.service.ts | 16 +++---- .../mfe-default-interceptors.prod.ts | 4 +- .../src/ts/modules/mfe/mfe-webbpm.module.ts | 3 ++ .../mfe/provider/mfe-permission.provider.ts | 8 ++++ 28 files changed, 227 insertions(+), 115 deletions(-) rename backend/src/main/java/ru/micord/ervu/account_applications/security/model/{jwt => }/UserSession.java (72%) create mode 100644 backend/src/main/java/ru/micord/ervu/account_applications/security/model/jwt/UserClaims.java create mode 100644 backend/src/main/java/ru/micord/ervu/account_applications/security/model/role/ErvuPermissionAuthority.java delete mode 100644 backend/src/main/java/ru/micord/ervu/account_applications/security/model/role/ErvuRoleAuthority.java create mode 100644 frontend/src/ts/modules/app/interceptor/permission.interceptor.service.ts create mode 100644 frontend/src/ts/modules/app/provider/permission.provider.ts create mode 100644 frontend/src/ts/modules/mfe/provider/mfe-permission.provider.ts diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/component/service/AccountGridLoadService.java b/backend/src/main/java/ru/micord/ervu/account_applications/component/service/AccountGridLoadService.java index b9e71887..5d1f9045 100644 --- a/backend/src/main/java/ru/micord/ervu/account_applications/component/service/AccountGridLoadService.java +++ b/backend/src/main/java/ru/micord/ervu/account_applications/component/service/AccountGridLoadService.java @@ -18,8 +18,7 @@ import ru.micord.ervu.account_applications.component.model.dto.SearchRequest; import ru.micord.ervu.account_applications.component.model.dto.SearchResponse; import ru.micord.ervu.account_applications.dao.RecruitmentDao; import ru.micord.ervu.account_applications.security.context.SecurityContext; -import ru.micord.ervu.account_applications.security.model.jwt.UserSession; -import ru.micord.ervu.account_applications.security.model.role.ErvuRoleAuthority; +import ru.micord.ervu.account_applications.security.model.role.ErvuPermissionAuthority; import ru.micord.ervu.account_applications.service.AccountFetchService; import ru.micord.ervu.account_applications.util.StringUtils; import service.GridService; @@ -110,10 +109,9 @@ public class AccountGridLoadService extends Behavior implements GridService { Filter[] filters) { int page = (offset / limit) + 1; Map filterMap = new HashMap<>(); - UserSession userSession = securityContext.getUserSession(); - Set roles = userSession.roles(); + Set permissions = securityContext.getPermissions(); if (ervuSecurityRole == null - || roles.stream().noneMatch(role -> role.getAuthority().equals(ervuSecurityRole))) { + || permissions.stream().noneMatch(permission -> permission.getAuthority().equals(ervuSecurityRole))) { List domainIds = recruitmentDao.getRecruitmentIdsWithParentByDomainId(securityContext.getDomainId()); filterMap.put(DOMAIN_IDS, domainIds.toArray()); } diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/component/service/AuditFormDaoImpl.java b/backend/src/main/java/ru/micord/ervu/account_applications/component/service/AuditFormDaoImpl.java index 57c5979e..008fcf22 100644 --- a/backend/src/main/java/ru/micord/ervu/account_applications/component/service/AuditFormDaoImpl.java +++ b/backend/src/main/java/ru/micord/ervu/account_applications/component/service/AuditFormDaoImpl.java @@ -11,7 +11,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import ru.micord.ervu.account_applications.component.dao.AuditDao; import ru.micord.ervu.account_applications.security.context.SecurityContext; -import ru.micord.ervu.account_applications.security.model.jwt.UserSession; +import ru.micord.ervu.account_applications.security.model.UserSession; import utils.DateTimeUtil; import ru.cg.webbpm.modules.database.api.bean.TableFieldData; diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/component/service/RecruitmentGridService.java b/backend/src/main/java/ru/micord/ervu/account_applications/component/service/RecruitmentGridService.java index 321ab4f2..800d69d3 100644 --- a/backend/src/main/java/ru/micord/ervu/account_applications/component/service/RecruitmentGridService.java +++ b/backend/src/main/java/ru/micord/ervu/account_applications/component/service/RecruitmentGridService.java @@ -13,8 +13,7 @@ import model.grid.SortInfo; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import ru.micord.ervu.account_applications.dao.RecruitmentDao; -import ru.micord.ervu.account_applications.security.model.jwt.UserSession; -import ru.micord.ervu.account_applications.security.model.role.ErvuRoleAuthority; +import ru.micord.ervu.account_applications.security.model.role.ErvuPermissionAuthority; import service.GridV2ServiceImpl; import ru.cg.webbpm.modules.database.api.dao.option.LoadOptions; @@ -49,12 +48,11 @@ public class RecruitmentGridService extends GridV2ServiceImpl { @Override public GridRows loadData(Integer offset, Integer limit, Filter[] filters, SortInfo[] sortInfos) { - UserSession userSession = securityContext.getUserSession(); - Set roles = userSession.roles(); + Set permissions = securityContext.getPermissions(); List recruitmentIds; List updatedFilters = new ArrayList<>(Arrays.asList(filters)); Optional recruitmentFilterOpt = findRecruitmentFilter(updatedFilters); - if (ervuSecurityRole != null && roles.stream().anyMatch(role -> role.getAuthority().equals( + if (ervuSecurityRole != null && permissions.stream().anyMatch(permission -> permission.getAuthority().equals( ervuSecurityRole))) { recruitmentIds = recruitmentFilterOpt.map( filter -> getChildRecruitmentIds(updatedFilters, filter)) diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/component/service/TreeItemService.java b/backend/src/main/java/ru/micord/ervu/account_applications/component/service/TreeItemService.java index 204bac93..ee5e4dd8 100644 --- a/backend/src/main/java/ru/micord/ervu/account_applications/component/service/TreeItemService.java +++ b/backend/src/main/java/ru/micord/ervu/account_applications/component/service/TreeItemService.java @@ -14,8 +14,7 @@ import org.springframework.stereotype.Service; import ru.micord.ervu.account_applications.component.model.TreeItemDto; import ru.micord.ervu.account_applications.component.rpc.TreeItemRpcService; import ru.micord.ervu.account_applications.security.context.SecurityContext; -import ru.micord.ervu.account_applications.security.model.jwt.UserSession; -import ru.micord.ervu.account_applications.security.model.role.ErvuRoleAuthority; +import ru.micord.ervu.account_applications.security.model.role.ErvuPermissionAuthority; import ru.cg.webbpm.modules.database.api.bean.TableRow; import ru.cg.webbpm.modules.database.api.dao.option.LoadOptions; @@ -56,10 +55,10 @@ public class TreeItemService { public List loadTreeData() { String domainId = securityContext.getDomainId(); - UserSession userSession = securityContext.getUserSession(); - Set roles = userSession.roles(); + Set permissions = securityContext.getPermissions(); + List filteredTreeItems = loadTreeItems(); - if (ervuSecurityRole == null || roles.stream().noneMatch(role -> role.getAuthority().equals( + if (ervuSecurityRole == null || permissions.stream().noneMatch(permission -> permission.getAuthority().equals( ervuSecurityRole))) { filteredTreeItems = filteredTreeItems.stream() .filter(item -> item.domainId.equalsIgnoreCase(domainId)) diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/security/config/SecurityConfig.java b/backend/src/main/java/ru/micord/ervu/account_applications/security/config/SecurityConfig.java index 8459853d..eb4c0a3e 100644 --- a/backend/src/main/java/ru/micord/ervu/account_applications/security/config/SecurityConfig.java +++ b/backend/src/main/java/ru/micord/ervu/account_applications/security/config/SecurityConfig.java @@ -1,7 +1,6 @@ package ru.micord.ervu.account_applications.security.config; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; @@ -26,21 +25,12 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private ErvuJwtAuthenticationProvider jwtAuthenticationProvider; - @Value("${security.roles.allowed:#{null}}") - private String[] allowedRoles; - protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeHttpRequests(auth -> { auth.antMatchers("/version").permitAll(); auth.antMatchers("/session").authenticated(); - - if (allowedRoles != null && allowedRoles.length > 0) { - auth.anyRequest().hasAnyAuthority(allowedRoles); - } - else { - auth.anyRequest().authenticated(); - } + auth.anyRequest().authenticated(); }) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/security/context/SecurityContext.java b/backend/src/main/java/ru/micord/ervu/account_applications/security/context/SecurityContext.java index aee7cdb2..476803a1 100644 --- a/backend/src/main/java/ru/micord/ervu/account_applications/security/context/SecurityContext.java +++ b/backend/src/main/java/ru/micord/ervu/account_applications/security/context/SecurityContext.java @@ -1,11 +1,16 @@ package ru.micord.ervu.account_applications.security.context; -import ru.micord.ervu.account_applications.security.model.jwt.UserSession; +import java.util.Set; + +import ru.micord.ervu.account_applications.security.model.UserSession; +import ru.micord.ervu.account_applications.security.model.role.ErvuPermissionAuthority; public interface SecurityContext { String getDomainId(); + Set getPermissions(); + String getUserId(); String getToken(); diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/security/context/SecurityContextImpl.java b/backend/src/main/java/ru/micord/ervu/account_applications/security/context/SecurityContextImpl.java index 613fac16..d09be684 100644 --- a/backend/src/main/java/ru/micord/ervu/account_applications/security/context/SecurityContextImpl.java +++ b/backend/src/main/java/ru/micord/ervu/account_applications/security/context/SecurityContextImpl.java @@ -1,9 +1,12 @@ package ru.micord.ervu.account_applications.security.context; +import java.util.Set; + import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; -import ru.micord.ervu.account_applications.security.model.jwt.UserSession; +import ru.micord.ervu.account_applications.security.model.UserSession; import ru.micord.ervu.account_applications.security.model.jwt.authentication.JwtTokenAuthentication; +import ru.micord.ervu.account_applications.security.model.role.ErvuPermissionAuthority; @Component @@ -15,6 +18,12 @@ public class SecurityContextImpl return auth != null ? auth.getUserSession().domainId() : null; } + @Override + public Set getPermissions() { + JwtTokenAuthentication auth = (JwtTokenAuthentication) SecurityContextHolder.getContext().getAuthentication(); + return auth != null ? auth.getUserSession().permissions() : null; + } + @Override public String getUserId() { JwtTokenAuthentication auth = (JwtTokenAuthentication) SecurityContextHolder.getContext().getAuthentication(); diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/security/controller/AuthorizationController.java b/backend/src/main/java/ru/micord/ervu/account_applications/security/controller/AuthorizationController.java index bb36f0d9..2f0f0502 100644 --- a/backend/src/main/java/ru/micord/ervu/account_applications/security/controller/AuthorizationController.java +++ b/backend/src/main/java/ru/micord/ervu/account_applications/security/controller/AuthorizationController.java @@ -6,8 +6,8 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import ru.micord.ervu.account_applications.security.context.SecurityContext; import ru.micord.ervu.account_applications.security.dto.SessionDto; -import ru.micord.ervu.account_applications.security.model.jwt.UserSession; -import ru.micord.ervu.account_applications.security.model.role.ErvuRoleAuthority; +import ru.micord.ervu.account_applications.security.model.UserSession; +import ru.micord.ervu.account_applications.security.model.role.ErvuPermissionAuthority; @RestController @@ -26,9 +26,9 @@ public class AuthorizationController { userSession.userId(), userSession.name(), userSession.realm(), - userSession.roles() + userSession.permissions() .stream() - .map(ErvuRoleAuthority::getAuthority) + .map(ErvuPermissionAuthority::getAuthority) .collect(Collectors.toSet()), userSession.domainId() ); diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/security/dto/SessionDto.java b/backend/src/main/java/ru/micord/ervu/account_applications/security/dto/SessionDto.java index cb1ffb1c..ac37eb79 100644 --- a/backend/src/main/java/ru/micord/ervu/account_applications/security/dto/SessionDto.java +++ b/backend/src/main/java/ru/micord/ervu/account_applications/security/dto/SessionDto.java @@ -6,6 +6,6 @@ public record SessionDto( String userId, String name, String realm, - Set roles, + Set permissions, String domainId ) {} diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/security/filter/JwtAuthenticationFilter.java b/backend/src/main/java/ru/micord/ervu/account_applications/security/filter/JwtAuthenticationFilter.java index a1cd10b6..2493a71f 100644 --- a/backend/src/main/java/ru/micord/ervu/account_applications/security/filter/JwtAuthenticationFilter.java +++ b/backend/src/main/java/ru/micord/ervu/account_applications/security/filter/JwtAuthenticationFilter.java @@ -56,9 +56,10 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { if (token == null) { return null; } - Authentication authentication = new JwtTokenDummy(token); - authentication = authenticationManager.authenticate(authentication); - return authentication; + + String permissionsHeader = extractPermissionsHeaderFromRequest(request); + Authentication authentication = new JwtTokenDummy(token, permissionsHeader); + return authenticationManager.authenticate(authentication); } private String extractAuthTokenFromRequest(HttpServletRequest request) { @@ -68,4 +69,9 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { .map(String::trim) .orElse(null); } + + private String extractPermissionsHeaderFromRequest(HttpServletRequest request) { + return Optional.ofNullable(request.getHeader("X-User-Permissions")) + .orElse(""); + } } diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/security/model/jwt/UserSession.java b/backend/src/main/java/ru/micord/ervu/account_applications/security/model/UserSession.java similarity index 72% rename from backend/src/main/java/ru/micord/ervu/account_applications/security/model/jwt/UserSession.java rename to backend/src/main/java/ru/micord/ervu/account_applications/security/model/UserSession.java index 278bd5a5..8359dd66 100644 --- a/backend/src/main/java/ru/micord/ervu/account_applications/security/model/jwt/UserSession.java +++ b/backend/src/main/java/ru/micord/ervu/account_applications/security/model/UserSession.java @@ -1,14 +1,14 @@ -package ru.micord.ervu.account_applications.security.model.jwt; +package ru.micord.ervu.account_applications.security.model; import java.util.Set; -import ru.micord.ervu.account_applications.security.model.role.ErvuRoleAuthority; +import ru.micord.ervu.account_applications.security.model.role.ErvuPermissionAuthority; public record UserSession( String userId, String name, String realm, - Set roles, + Set permissions, String domainId ) { @@ -20,7 +20,7 @@ public record UserSession( private String userId; private String name; private String realm; - private Set roles; + private Set permissions; private String domainId; private UserSessionBuilder() { @@ -41,8 +41,8 @@ public record UserSession( return this; } - public UserSessionBuilder withRoles(Set roles) { - this.roles = roles; + public UserSessionBuilder withPermissions(Set permissions) { + this.permissions = permissions; return this; } @@ -52,7 +52,7 @@ public record UserSession( } public UserSession build() { - return new UserSession(userId, name, realm, roles, domainId); + return new UserSession(userId, name, realm, permissions, domainId); } } } diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/security/model/jwt/UserClaims.java b/backend/src/main/java/ru/micord/ervu/account_applications/security/model/jwt/UserClaims.java new file mode 100644 index 00000000..66e5c3e4 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/account_applications/security/model/jwt/UserClaims.java @@ -0,0 +1,47 @@ +package ru.micord.ervu.account_applications.security.model.jwt; + +public record UserClaims( + String userId, + String name, + String realm, + String domainId +) { + + public static UserClaimsBuilder builder() { + return new UserClaimsBuilder(); + } + + public static final class UserClaimsBuilder { + private String userId; + private String name; + private String realm; + private String domainId; + + private UserClaimsBuilder() { + } + + public UserClaimsBuilder withUserId(String userId) { + this.userId = userId; + return this; + } + + public UserClaimsBuilder withName(String name) { + this.name = name; + return this; + } + + public UserClaimsBuilder withRealm(String realm) { + this.realm = realm; + return this; + } + + public UserClaimsBuilder withDomainId(String domainId) { + this.domainId = domainId; + return this; + } + + public UserClaims build() { + return new UserClaims(userId, name, realm, domainId); + } + } +} diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/security/model/jwt/authentication/JwtTokenAuthentication.java b/backend/src/main/java/ru/micord/ervu/account_applications/security/model/jwt/authentication/JwtTokenAuthentication.java index 54283656..0899cd39 100644 --- a/backend/src/main/java/ru/micord/ervu/account_applications/security/model/jwt/authentication/JwtTokenAuthentication.java +++ b/backend/src/main/java/ru/micord/ervu/account_applications/security/model/jwt/authentication/JwtTokenAuthentication.java @@ -4,7 +4,7 @@ import java.util.Collection; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; -import ru.micord.ervu.account_applications.security.model.jwt.UserSession; +import ru.micord.ervu.account_applications.security.model.UserSession; public class JwtTokenAuthentication implements Authentication { @@ -22,7 +22,7 @@ public class JwtTokenAuthentication implements Authentication { @Override public Collection getAuthorities() { - return userSession.roles(); + return userSession.permissions(); } @Override diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/security/model/jwt/authentication/JwtTokenDummy.java b/backend/src/main/java/ru/micord/ervu/account_applications/security/model/jwt/authentication/JwtTokenDummy.java index 33e5b92f..ac5edb33 100644 --- a/backend/src/main/java/ru/micord/ervu/account_applications/security/model/jwt/authentication/JwtTokenDummy.java +++ b/backend/src/main/java/ru/micord/ervu/account_applications/security/model/jwt/authentication/JwtTokenDummy.java @@ -9,15 +9,21 @@ import org.springframework.security.core.GrantedAuthority; public class JwtTokenDummy implements Authentication { private final String token; + private final String permissionsHeader; - public JwtTokenDummy(String token) { + public JwtTokenDummy(String token, String permissionsHeader) { this.token = token; + this.permissionsHeader = permissionsHeader; } public String getToken() { return token; } + public String getPermissionsHeader() { + return permissionsHeader; + } + @Override public Collection getAuthorities() { throw new NotImplementedException("Not implemented for dummy"); diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/security/model/role/ErvuPermissionAuthority.java b/backend/src/main/java/ru/micord/ervu/account_applications/security/model/role/ErvuPermissionAuthority.java new file mode 100644 index 00000000..107c9589 --- /dev/null +++ b/backend/src/main/java/ru/micord/ervu/account_applications/security/model/role/ErvuPermissionAuthority.java @@ -0,0 +1,16 @@ +package ru.micord.ervu.account_applications.security.model.role; + +import org.springframework.security.core.GrantedAuthority; + +public class ErvuPermissionAuthority implements GrantedAuthority { + private final String permission; + + public ErvuPermissionAuthority(String permission) { + this.permission = permission; + } + + @Override + public String getAuthority() { + return permission; + } +} diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/security/model/role/ErvuRoleAuthority.java b/backend/src/main/java/ru/micord/ervu/account_applications/security/model/role/ErvuRoleAuthority.java deleted file mode 100644 index 8ca0d84e..00000000 --- a/backend/src/main/java/ru/micord/ervu/account_applications/security/model/role/ErvuRoleAuthority.java +++ /dev/null @@ -1,17 +0,0 @@ -package ru.micord.ervu.account_applications.security.model.role; - -import org.springframework.security.core.GrantedAuthority; - -public class ErvuRoleAuthority implements GrantedAuthority { - - private final String role; - - public ErvuRoleAuthority(String role) { - this.role = role; - } - - @Override - public String getAuthority() { - return role; - } -} diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/security/provider/ErvuJwtAuthenticationProvider.java b/backend/src/main/java/ru/micord/ervu/account_applications/security/provider/ErvuJwtAuthenticationProvider.java index 198cd59b..127edda1 100644 --- a/backend/src/main/java/ru/micord/ervu/account_applications/security/provider/ErvuJwtAuthenticationProvider.java +++ b/backend/src/main/java/ru/micord/ervu/account_applications/security/provider/ErvuJwtAuthenticationProvider.java @@ -1,12 +1,20 @@ package ru.micord.ervu.account_applications.security.provider; +import java.util.Arrays; +import java.util.Collections; +import java.util.Set; +import java.util.stream.Collectors; + import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.stereotype.Component; -import ru.micord.ervu.account_applications.security.model.jwt.UserSession; +import org.springframework.util.StringUtils; +import ru.micord.ervu.account_applications.security.model.jwt.UserClaims; +import ru.micord.ervu.account_applications.security.model.UserSession; import ru.micord.ervu.account_applications.security.model.jwt.authentication.JwtTokenAuthentication; import ru.micord.ervu.account_applications.security.model.jwt.authentication.JwtTokenDummy; +import ru.micord.ervu.account_applications.security.model.role.ErvuPermissionAuthority; import ru.micord.ervu.account_applications.security.service.JwtTokenService; @Component @@ -22,10 +30,32 @@ public class ErvuJwtAuthenticationProvider implements AuthenticationProvider { public Authentication authenticate(Authentication authentication) throws AuthenticationException { JwtTokenDummy jwtTokenDummy = (JwtTokenDummy) authentication; String jwtToken = jwtTokenDummy.getToken(); - UserSession userSession = jwtTokenService.getUserSession(jwtToken); + String permissionHeader = jwtTokenDummy.getPermissionsHeader(); + UserClaims userClaims = jwtTokenService.getUserClaims(jwtToken); + Set permissions = extractPermissionsFromHeader(permissionHeader); + UserSession userSession = UserSession.builder() + .withDomainId(userClaims.domainId()) + .withUserId(userClaims.userId()) + .withPermissions(permissions) + .withName(userClaims.name()) + .withRealm(userClaims.realm()) + .build(); return new JwtTokenAuthentication(userSession, jwtToken); } + private Set extractPermissionsFromHeader(String permissionsHeader) { + if (!StringUtils.hasText(permissionsHeader)) { + return Collections.emptySet(); + } + + return Arrays.stream(permissionsHeader.split(",")) + .map(String::trim) + .filter(StringUtils::hasText) + .map(String::toLowerCase) + .map(ErvuPermissionAuthority::new) + .collect(Collectors.toSet()); + } + @Override public boolean supports(Class authentication) { return JwtTokenDummy.class.isAssignableFrom(authentication); diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/security/service/ErvuJwtTokenService.java b/backend/src/main/java/ru/micord/ervu/account_applications/security/service/ErvuJwtTokenService.java index 6d3b6008..df27e7d8 100644 --- a/backend/src/main/java/ru/micord/ervu/account_applications/security/service/ErvuJwtTokenService.java +++ b/backend/src/main/java/ru/micord/ervu/account_applications/security/service/ErvuJwtTokenService.java @@ -3,11 +3,7 @@ package ru.micord.ervu.account_applications.security.service; import java.security.KeyFactory; import java.security.PublicKey; import java.security.spec.X509EncodedKeySpec; -import java.util.ArrayList; import java.util.Base64; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtException; @@ -16,8 +12,7 @@ import io.jsonwebtoken.Jwts; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import ru.micord.ervu.account_applications.security.exception.JwtProcessingException; -import ru.micord.ervu.account_applications.security.model.jwt.UserSession; -import ru.micord.ervu.account_applications.security.model.role.ErvuRoleAuthority; +import ru.micord.ervu.account_applications.security.model.jwt.UserClaims; @Service public class ErvuJwtTokenService implements JwtTokenService { @@ -37,24 +32,21 @@ public class ErvuJwtTokenService implements JwtTokenService { } @Override - public UserSession getUserSession(String token) { + public UserClaims getUserClaims(String token) { return enableJwtValidation ? parseToken(token) : unsafeParseToken(token); } - protected UserSession unsafeParseToken(String token) { + protected UserClaims unsafeParseToken(String token) { try { token = token.substring(0, token.lastIndexOf(".") + 1); JwtParser parser = Jwts.parser(); Claims claims = parser.parseClaimsJwt(token).getBody(); - List lowerCaseRoles = getLowerCaseRoles(claims); - - return UserSession.builder() + return UserClaims.builder() .withUserId(claims.getSubject()) .withName(claims.get("name", String.class)) .withRealm(claims.get("realm", String.class)) .withDomainId(claims.get("domain_id", String.class)) - .withRoles(getRoles(lowerCaseRoles)) .build(); } catch (JwtException e) { @@ -62,7 +54,7 @@ public class ErvuJwtTokenService implements JwtTokenService { } } - protected UserSession parseToken(String token) { + protected UserClaims parseToken(String token) { JwtParser parser = Jwts.parser(); try { if (issuer != null && !issuer.isEmpty()) { @@ -74,14 +66,12 @@ public class ErvuJwtTokenService implements JwtTokenService { } Claims claims = parser.parseClaimsJws(token).getBody(); - List lowerCaseRoles = getLowerCaseRoles(claims); - return UserSession.builder() + return UserClaims.builder() .withUserId(claims.getSubject()) .withName(claims.get("name", String.class)) .withRealm(claims.get("realm", String.class)) .withDomainId(claims.get("domain_id", String.class)) - .withRoles(getRoles(lowerCaseRoles)) .build(); } catch (JwtException e) { @@ -89,22 +79,6 @@ public class ErvuJwtTokenService implements JwtTokenService { } } - private List getLowerCaseRoles(Claims claims) { - List roleList = claims.get("roles", ArrayList.class); - return roleList != null ? - roleList.stream() - .map(String::toLowerCase) - .collect(Collectors.toList()) : - new ArrayList<>(); - } - - protected Set getRoles(List roles) { - return roles - .stream() - .map(ErvuRoleAuthority::new) - .collect(Collectors.toSet()); - } - private PublicKey extractPublicKey(String publicKeyStr) { try { byte[] decodedPublicKey = Base64.getDecoder().decode(publicKeyStr); diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/security/service/JwtTokenService.java b/backend/src/main/java/ru/micord/ervu/account_applications/security/service/JwtTokenService.java index fa72284c..fbe51707 100644 --- a/backend/src/main/java/ru/micord/ervu/account_applications/security/service/JwtTokenService.java +++ b/backend/src/main/java/ru/micord/ervu/account_applications/security/service/JwtTokenService.java @@ -1,8 +1,7 @@ package ru.micord.ervu.account_applications.security.service; - -import ru.micord.ervu.account_applications.security.model.jwt.UserSession; +import ru.micord.ervu.account_applications.security.model.jwt.UserClaims; public interface JwtTokenService { - UserSession getUserSession(String token); + UserClaims getUserClaims(String token); } diff --git a/backend/src/main/java/ru/micord/ervu/account_applications/service/UserApplicationListService.java b/backend/src/main/java/ru/micord/ervu/account_applications/service/UserApplicationListService.java index 02a8e1f7..824e37ef 100644 --- a/backend/src/main/java/ru/micord/ervu/account_applications/service/UserApplicationListService.java +++ b/backend/src/main/java/ru/micord/ervu/account_applications/service/UserApplicationListService.java @@ -8,7 +8,7 @@ import org.springframework.stereotype.Service; import ru.micord.ervu.account_applications.component.dao.AuditDao; import ru.micord.ervu.account_applications.dao.UserApplicationListDao; import ru.micord.ervu.account_applications.security.context.SecurityContext; -import ru.micord.ervu.account_applications.security.model.jwt.UserSession; +import ru.micord.ervu.account_applications.security.model.UserSession; import utils.DateTimeUtil; import static ru.micord.ervu.account_applications.enums.ApplicationStatus.ACCEPTED; diff --git a/frontend/src/ts/account_applications/ErvuCheckUserRole.ts b/frontend/src/ts/account_applications/ErvuCheckUserRole.ts index 218c5188..2bf37d3e 100644 --- a/frontend/src/ts/account_applications/ErvuCheckUserRole.ts +++ b/frontend/src/ts/account_applications/ErvuCheckUserRole.ts @@ -1,4 +1,4 @@ -import {AnalyticalScope, Behavior, Source, Visible} from "@webbpm/base-package"; +import {AnalyticalScope, Behavior, Visible} from "@webbpm/base-package"; import {AuthorizationService} from "../modules/app/service/authorization.service"; @AnalyticalScope(Behavior) @@ -11,7 +11,7 @@ export class ErvuCheckUserRole extends Behavior{ } @Visible() - public hasRole(@Source("roles") role: string): boolean { - return this.authService.hasRole(role); + public hasAnyPermission(permission: string []): boolean { + return this.authService.hasAnyPermission(permission); } } diff --git a/frontend/src/ts/mfe-app-tools.ts b/frontend/src/ts/mfe-app-tools.ts index f7b5b992..e8f95c2c 100644 --- a/frontend/src/ts/mfe-app-tools.ts +++ b/frontend/src/ts/mfe-app-tools.ts @@ -4,7 +4,13 @@ import {NgModuleRef} from "@angular/core"; let childEventHandlerFromContainer = null; -export type ChildEventType = 'navigate' | 'token-request' | 'ws-request' +export type ChildEventType = + 'navigate' + | 'token-request' + | 'ws-request' + | 'oidc-user-request' + | 'permissions-request' + export type ParentEventType = 'navigate'; export function fireMfeEventToContainer(eventType: ChildEventType, eventData: any): Promise { diff --git a/frontend/src/ts/modules/app/interceptor/permission.interceptor.service.ts b/frontend/src/ts/modules/app/interceptor/permission.interceptor.service.ts new file mode 100644 index 00000000..faf17827 --- /dev/null +++ b/frontend/src/ts/modules/app/interceptor/permission.interceptor.service.ts @@ -0,0 +1,28 @@ +import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http"; +import {from, Observable} from "rxjs"; +import { Injectable } from "@angular/core"; +import {PermissionProvider} from "../provider/permission.provider"; + +@Injectable({ providedIn: 'root' }) +export class PermissionInterceptor implements HttpInterceptor { + constructor(protected permissionProvider: PermissionProvider) { + } + + intercept(request: HttpRequest, next: HttpHandler): Observable> { + return from(this.handlePermissions(request, next)); + } + + private async handlePermissions(request: HttpRequest, + next: HttpHandler): Promise> { + const permissions = await this.permissionProvider.getUserPermissions(); + if (permissions.length > 0) { + request = request.clone({ + setHeaders: { + 'X-User-Permissions': permissions.join(',') + } + }); + } + + return next.handle(request).toPromise(); + } +} \ No newline at end of file diff --git a/frontend/src/ts/modules/app/provider/permission.provider.ts b/frontend/src/ts/modules/app/provider/permission.provider.ts new file mode 100644 index 00000000..d1423c56 --- /dev/null +++ b/frontend/src/ts/modules/app/provider/permission.provider.ts @@ -0,0 +1,5 @@ +export class PermissionProvider { + getUserPermissions(): Promise { + return null; + } +} \ No newline at end of file diff --git a/frontend/src/ts/modules/app/service/authorization.service.ts b/frontend/src/ts/modules/app/service/authorization.service.ts index 25fb045b..0c68170f 100644 --- a/frontend/src/ts/modules/app/service/authorization.service.ts +++ b/frontend/src/ts/modules/app/service/authorization.service.ts @@ -9,7 +9,7 @@ export interface UserSession { name: string, realm: string, domainId: string, - roles: string[] + permissions: string[] } @Injectable({providedIn: 'root'}) @@ -30,7 +30,7 @@ export class AuthorizationService implements OnDestroy { this.session = session; this.onSessionUpdate.next(session); - if (this.hasRole('security_administrator')) { + if (this.hasPermission('security_administrator')) { this.websocketService.subscribe(({data}) => { let parsedObj = JSON.parse(data); @@ -49,12 +49,12 @@ export class AuthorizationService implements OnDestroy { return !!this.session; } - hasAnyRole(roles: string[]): boolean { - return this.isAuthorized() && roles.some(role => this.getRoles().includes(role)); + hasAnyPermission(permissions: string[]): boolean { + return this.isAuthorized() && permissions.some(role => this.getPermissions().includes(role)); } - hasRole(role: string): boolean { - return this.isAuthorized() && this.getRoles().includes(role); + hasPermission(permission: string): boolean { + return this.isAuthorized() && this.getPermissions().includes(permission); } getUserId(): string{ @@ -73,8 +73,8 @@ export class AuthorizationService implements OnDestroy { return this.isAuthorized() ? this.session.domainId : null; } - getRoles(): string[] { - return this.isAuthorized() ? this.session.roles : null; + getPermissions(): string[] { + return this.isAuthorized() ? this.session.permissions : null; } ngOnDestroy(): void { diff --git a/frontend/src/ts/modules/mfe/interceptor/mfe-default-interceptors.prod.ts b/frontend/src/ts/modules/mfe/interceptor/mfe-default-interceptors.prod.ts index 2074cd04..19a75d35 100644 --- a/frontend/src/ts/modules/mfe/interceptor/mfe-default-interceptors.prod.ts +++ b/frontend/src/ts/modules/mfe/interceptor/mfe-default-interceptors.prod.ts @@ -5,11 +5,13 @@ import { HttpSecurityInterceptor } from "@webbpm/base-package"; import {TokenInterceptor} from "../../app/interceptor/token.interceptor.service"; +import {PermissionInterceptor} from "../../app/interceptor/permission.interceptor.service"; export const DEFAULT_HTTP_INTERCEPTOR_PROVIDERS = [ {provide: HTTP_INTERCEPTORS, useClass: HttpSecurityInterceptor, multi: true}, {provide: HTTP_INTERCEPTORS, useClass: HttpSecurityErrorInterceptor, multi: true}, {provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true}, - {provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true} + {provide: HTTP_INTERCEPTORS, useClass: TokenInterceptor, multi: true}, + {provide: HTTP_INTERCEPTORS, useClass: PermissionInterceptor, multi: true} ]; \ No newline at end of file diff --git a/frontend/src/ts/modules/mfe/mfe-webbpm.module.ts b/frontend/src/ts/modules/mfe/mfe-webbpm.module.ts index 8e9339f3..ef90c8ce 100644 --- a/frontend/src/ts/modules/mfe/mfe-webbpm.module.ts +++ b/frontend/src/ts/modules/mfe/mfe-webbpm.module.ts @@ -28,6 +28,8 @@ import {TokenProvider} from "../app/provider/token.provider"; import {MfeTokenProvider} from "./provider/mfe-token.provider"; import {DEFAULT_HTTP_INTERCEPTOR_PROVIDERS} from "./interceptor/mfe-default-interceptors.prod"; import {MfeOverlayContainer} from "./overlay/mfe-overlay-container.service"; +import {PermissionProvider} from "../app/provider/permission.provider"; +import {MfePermissionProvider} from "./provider/mfe-permission.provider"; let IMPORTS = [ @@ -61,6 +63,7 @@ let IMPORTS = [ {provide: RolesGuard, useClass: MfeRolesGuard}, {provide: TokenProvider, useClass: MfeTokenProvider}, {provide: OverlayContainer, useClass: MfeOverlayContainer}, + {provide: PermissionProvider, useClass: MfePermissionProvider}, DEFAULT_HTTP_INTERCEPTOR_PROVIDERS ], bootstrap: [ diff --git a/frontend/src/ts/modules/mfe/provider/mfe-permission.provider.ts b/frontend/src/ts/modules/mfe/provider/mfe-permission.provider.ts new file mode 100644 index 00000000..478a2482 --- /dev/null +++ b/frontend/src/ts/modules/mfe/provider/mfe-permission.provider.ts @@ -0,0 +1,8 @@ +import {PermissionProvider} from "../../app/provider/permission.provider"; +import {fireMfeEventToContainer} from "../../../mfe-app-tools"; + +export class MfePermissionProvider extends PermissionProvider { + getUserPermissions(): Promise { + return fireMfeEventToContainer('permissions-request', {}); + } +} \ No newline at end of file