Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/organization UI permissions #644

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
73 commits
Select commit Hold shift + click to select a range
ef89f61
Merge 'feature/organization-permission-mapping' into 'feature/organiz…
peyman-mohtashami Dec 3, 2021
121671b
Merge Fix getting and updating roles into feature/organization-ui
peyman-mohtashami Dec 6, 2021
733b4df
Add permission tab to projects and organizations
peyman-mohtashami Dec 8, 2021
2edc49f
Merge origin/feature/organization-ui into feature/organization-ui
peyman-mohtashami Dec 8, 2021
2b3161e
Modify permission tabs
peyman-mohtashami Dec 8, 2021
b7eb3b3
Add project transfering between organizations
peyman-mohtashami Dec 9, 2021
4c33a20
Fix e2e tests
peyman-mohtashami Dec 9, 2021
12fc74c
Add translations
peyman-mohtashami Dec 9, 2021
ba4d82a
Add missed translations
peyman-mohtashami Dec 9, 2021
1c3d2d9
Add missed translations
peyman-mohtashami Dec 9, 2021
6bb04a9
Fix organization name
peyman-mohtashami Dec 15, 2021
1b53789
Remove delete button in organizations and redundant view buttons in o…
peyman-mohtashami Dec 15, 2021
f5b7b32
Misc fixes and simplifications
blootsvoets Dec 15, 2021
5b8fdf9
Add demo oadmin user
blootsvoets Dec 21, 2021
08226c7
Add permission tests
peyman-mohtashami Dec 21, 2021
6d6317a
Merge branch 'feature/organization-ui-permissions' of https://github.…
peyman-mohtashami Dec 21, 2021
70c0348
Misc fixes
blootsvoets Dec 21, 2021
8b2dda4
Merge branch 'feature/organization-ui-permissions' into feature/organ…
blootsvoets Dec 21, 2021
1397bf3
Permission fixes
blootsvoets Dec 21, 2021
4c33883
Organization user fixes
blootsvoets Dec 21, 2021
fdeac6f
Add missed users in role_user.scv
peyman-mohtashami Dec 21, 2021
f52bdd5
Merge pull request #649 from RADAR-base/feature/organization-ui-permi…
blootsvoets Dec 22, 2021
9e12f3c
Merge branch 'feature/organization-ui' into feature/organization-ui-p…
blootsvoets Dec 22, 2021
32c7b43
Fix typo's
blootsvoets Dec 22, 2021
5bc5b5f
Merge origin/feature/organization-ui-permissions into feature/organiz…
peyman-mohtashami Dec 22, 2021
5ad8187
Fix default value permissions
blootsvoets Dec 22, 2021
47aa1f7
Fix organization admin csv tables
blootsvoets Dec 22, 2021
b6e2423
Fix changeset
blootsvoets Dec 22, 2021
ca6755b
Merge branch 'feature/organization-ui-permissions' of https://github.…
peyman-mohtashami Dec 22, 2021
d749e39
Fix retry syntax
blootsvoets Jan 4, 2022
5b2d420
Modify route guards and add role_organization_admin
peyman-mohtashami Jan 5, 2022
5e45703
Take one result from find method
peyman-mohtashami Jan 5, 2022
6540a12
Use copy of entity in forms
peyman-mohtashami Jan 5, 2022
006f800
Show/hide tabs according to user role
peyman-mohtashami Jan 5, 2022
9582813
Modify routerLink of edit project
peyman-mohtashami Jan 5, 2022
547e6c1
Fix subscription and modal cleanup
blootsvoets Jan 6, 2022
fa9baaa
Get all users as ADMIN
blootsvoets Jan 6, 2022
f03c027
Small fixes
blootsvoets Jan 6, 2022
1598b08
Merge remote-tracking branch 'origin/feature/organization-ui-permissi…
blootsvoets Jan 6, 2022
4edeb07
Small e2e fixes
blootsvoets Jan 6, 2022
492f852
Make account and admin components use proper observables
blootsvoets Jan 6, 2022
e8ca30a
Better filtering
blootsvoets Jan 6, 2022
d195fe0
Fix liquibase syntax
blootsvoets Jan 7, 2022
56fa740
Misc fixes
blootsvoets Jan 7, 2022
bafb657
Removed unused console.log
blootsvoets Jan 7, 2022
e72ff8e
Don't sort on every page load
blootsvoets Jan 7, 2022
696eabb
Make log sorting meaning more clear
blootsvoets Jan 7, 2022
e471292
Simplify paging/sorting
blootsvoets Jan 7, 2022
f6f258f
Don't need organization admin for updating permissions
blootsvoets Jan 10, 2022
cb731f9
Fix gradle test
blootsvoets Jan 11, 2022
a75a301
Fix guards and permissions
peyman-mohtashami Jan 11, 2022
a0e8b1f
Fix e2e test
peyman-mohtashami Jan 11, 2022
222855f
Merge branch 'fix-org-retry' into fix-route-guards
peyman-mohtashami Jan 11, 2022
093c49a
Fix permission management in combination with project tabs
blootsvoets Jan 17, 2022
5d35582
Make organization detail page more similar to project detail page
blootsvoets Jan 17, 2022
e6fe3d2
Fix tests
blootsvoets Jan 17, 2022
5ecc507
Back button improvements
blootsvoets Jan 17, 2022
db98faa
Readbility navbar
blootsvoets Jan 17, 2022
28c0343
Fix update ui on updating an organization or project
peyman-mohtashami Jan 18, 2022
0b05375
Merge pull request #651 from RADAR-base/fix-route-guards
blootsvoets Jan 19, 2022
06a43fa
Merge branch 'fix-route-guards' into fix-org-retry
blootsvoets Jan 19, 2022
dff3a41
Merge branch 'fix-org-retry' into fix-update-org-project
blootsvoets Jan 20, 2022
caa8b20
Don't store user session if it no longer exists
blootsvoets Jan 20, 2022
3b42cd9
Small reversal
blootsvoets Jan 20, 2022
321fdef
Test fixes
blootsvoets Jan 20, 2022
24565ba
Remove redundant project reset
blootsvoets Jan 20, 2022
f5ecab8
Find distinct project only
blootsvoets Jan 20, 2022
3e369ef
Merge pull request #653 from RADAR-base/fix-update-org-project
blootsvoets Jan 20, 2022
aaa9bf7
Readability fix
blootsvoets Jan 20, 2022
da13ba9
Remove promises where possible
blootsvoets Jan 20, 2022
d9a1466
Fix save account test
blootsvoets Jan 20, 2022
8e077b3
Merge pull request #650 from RADAR-base/fix-org-retry
blootsvoets Jan 20, 2022
cf2a129
Ensure that gradle is stopped
blootsvoets Jan 20, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,10 @@ jobs:
run: |
cp src/test/resources/config/keystore.p12 src/main/resources/config/keystore.p12
./gradlew bootRun &>mp.log </dev/null &
BOOTRUN_PROCESS=$!
yarn run wait-for-managementportal
./gradlew generateOpenApiSpec
yarn e2e
kill $BOOTRUN_PROCESS
./gradlew --stop

- name: Has SNAPSHOT version
id: is-snapshot
Expand Down
4 changes: 1 addition & 3 deletions config/checkstyle/checkstyle.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,7 @@
<property name="tokens" value="LITERAL_TRY, LITERAL_FINALLY, LITERAL_IF, LITERAL_ELSE, LITERAL_SWITCH"/>
</module>
<module name="NeedBraces"/>
<module name="LeftCurly">
</module>
<module name="RightCurly"/>
<module name="LeftCurly"/>
<module name="RightCurly">
<property name="option" value="alone"/>
<property name="tokens" value="CLASS_DEF, METHOD_DEF, CTOR_DEF, LITERAL_FOR, LITERAL_WHILE, LITERAL_DO, STATIC_INIT, INSTANCE_INIT"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.radarbase.management.config;

import org.radarbase.auth.authorization.RoleAuthority;
import org.radarbase.management.repository.UserRepository;
import org.radarbase.management.security.ClaimsTokenEnhancer;
import org.radarbase.management.security.Http401UnauthorizedEntryPoint;
import org.radarbase.management.security.JwtAuthenticationFilter;
Expand Down Expand Up @@ -112,9 +113,12 @@ protected static class ResourceServerConfiguration extends ResourceServerConfigu
@Autowired
private AuthenticationManager authenticationManager;

@Autowired
private UserRepository userRepository;

public JwtAuthenticationFilter jwtAuthenticationFilter() {
return new JwtAuthenticationFilter(
keyStoreHandler.getTokenValidator(), authenticationManager)
keyStoreHandler.getTokenValidator(), authenticationManager, userRepository)
.skipUrlPattern(HttpMethod.GET, "/management/health")
.skipUrlPattern(HttpMethod.GET, "/api/meta-token/*");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ public void add(Predicate predicate) {
predicates.add(predicate);
}

public void isNull(Expression<?> expression) {
predicates.add(builder.isNull(expression));
}

/**
* Build the predicates as an AND predicate.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.From;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.persistence.criteria.Subquery;
Expand All @@ -40,7 +41,7 @@ public Predicate toPredicate(Root<User> root, @Nonnull CriteriaQuery<?> query,
predicates.likeLower(root.get("login"), login);
predicates.likeLower(root.get("email"), email);

filterRoles(predicates, root.join("roles"), query);
filterRoles(predicates, root.join("roles", JoinType.LEFT), query);

query.distinct(true);
var result = predicates.toAndPredicate();
Expand All @@ -52,11 +53,13 @@ private void filterRoles(PredicateBuilder predicates, Join<User, Role> roleJoin,
CriteriaQuery<?> query) {
Stream<RoleAuthority> authoritiesFiltered = Stream.of(RoleAuthority.values())
.filter(java.util.function.Predicate.not(RoleAuthority::isPersonal));
boolean allowNoRole = true;

if (predicates.isValidValue(authority)) {
String authorityUpper = authority.toUpperCase(Locale.ROOT);
authoritiesFiltered = authoritiesFiltered
.filter(r -> r.authority().contains(authorityUpper));
.filter(r -> r != null && r.authority().contains(authorityUpper));
allowNoRole = false;
}
List<RoleAuthority> authoritiesAllowed = authoritiesFiltered.collect(Collectors.toList());
if (authoritiesAllowed.isEmpty()) {
Expand All @@ -66,18 +69,21 @@ private void filterRoles(PredicateBuilder predicates, Join<User, Role> roleJoin,
return;
}

determineScope(predicates, roleJoin, query, authoritiesAllowed);
determineScope(predicates, roleJoin, query, authoritiesAllowed, allowNoRole);
}

private void determineScope(
PredicateBuilder predicates,
Join<User, Role> roleJoin,
CriteriaQuery<?> query,
List<RoleAuthority> authoritiesAllowed) {
List<RoleAuthority> authoritiesAllowed,
boolean allowNoRole) {
PredicateBuilder authorityPredicates = predicates.newBuilder();

boolean allowNoRoleInScope = allowNoRole;
// Is organization admin
if (predicates.isValidValue(projectName)) {
allowNoRoleInScope = false;
// Is project admin
entitySubquery(RoleAuthority.Scope.PROJECT, roleJoin,
query, authorityPredicates, authoritiesAllowed,
Expand All @@ -91,6 +97,7 @@ private void determineScope(
org.join("projects").get("projectName"), projectName));
}
} else if (predicates.isValidValue(organization)) {
allowNoRoleInScope = false;
entitySubquery(RoleAuthority.Scope.ORGANIZATION, roleJoin,
query, authorityPredicates, authoritiesAllowed,
(b, org) -> b.likeLower(org.get("name"), organization));
Expand All @@ -104,6 +111,9 @@ private void determineScope(
addAllowedAuthorities(authorityPredicates, roleJoin, authoritiesAllowed,
RoleAuthority.Scope.GLOBAL);
}
if (allowNoRoleInScope) {
authorityPredicates.isNull(roleJoin.get("id"));
}

predicates.add(authorityPredicates.toOrPredicate());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.audit.AuditEvent;
import org.springframework.boot.actuate.audit.AuditEventRepository;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
Expand All @@ -44,13 +45,14 @@ public class ClaimsTokenEnhancer implements TokenEnhancer, InitializingBean {
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken,
OAuth2Authentication authentication) {
logger.debug("Enhancing token {} with authentication {}" , accessToken, authentication);
logger.debug("Enhancing token of authentication {}" , authentication);

Map<String, Object> additionalInfo = new HashMap<>();

String userName = authentication.getName();

if (authentication.getPrincipal() instanceof Principal) {
if (authentication.getPrincipal() instanceof Principal
|| authentication.getPrincipal() instanceof UserDetails) {
// add the 'sub' claim in accordance with JWT spec
additionalInfo.put("sub", userName);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

import org.radarbase.auth.authentication.TokenValidator;
import org.radarbase.auth.exception.TokenValidationException;
import org.radarbase.auth.token.AuthorityReference;
import org.radarbase.auth.token.RadarToken;
import org.radarbase.management.repository.UserRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
Expand All @@ -26,6 +28,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

/**
* Created by dverbeec on 29/09/2017.
Expand All @@ -36,16 +39,19 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final AuthenticationManager authenticationManager;
public static final String TOKEN_ATTRIBUTE = "jwt";
private final List<AntPathRequestMatcher> ignoreUrls;
private final UserRepository userRepository;

/**
* Authentication filter using given validator.
* @param validator validates the JWT token.
* @param authenticationManager authentication manager to pass valid authentication to.
*/
public JwtAuthenticationFilter(TokenValidator validator,
AuthenticationManager authenticationManager) {
AuthenticationManager authenticationManager,
UserRepository userRepository) {
this.validator = validator;
this.authenticationManager = authenticationManager;
this.userRepository = userRepository;
this.ignoreUrls = new ArrayList<>();
}

Expand Down Expand Up @@ -73,29 +79,58 @@ protected void doFilterInternal(@Nonnull HttpServletRequest httpRequest,
}

HttpSession session = httpRequest.getSession(false);
RadarToken token = null;
SessionRadarToken token = null;
if (session != null) {
token = (RadarToken) session.getAttribute(TOKEN_ATTRIBUTE);
token = SessionRadarToken.from((RadarToken) session.getAttribute(TOKEN_ATTRIBUTE));
}
if (token == null) {
try {
token = validator.validateAccessToken(getToken(httpRequest,
httpResponse));
token = SessionRadarToken.from(validator.validateAccessToken(getToken(httpRequest,
httpResponse)));
} catch (TokenValidationException ex) {
logger.error(ex.getMessage());
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.setHeader(HttpHeaders.WWW_AUTHENTICATE, OAuth2AccessToken.BEARER_TYPE);
httpResponse.getOutputStream().println(
"{\"error\": \"" + "Unauthorized" + ",\n"
+ "\"status\": \"" + HttpServletResponse.SC_UNAUTHORIZED + ",\n"
+ "\"message\": \"" + ex.getMessage() + ",\n"
+ "\"message\": \"" + ex.getMessage() + "\",\n"
+ "\"path\": \"" + httpRequest.getRequestURI() + "\n"
+ "\"}");
return;
}
} else if (!token.isClientCredentials()) {
var user = userRepository.findOneByLogin(token.getUsername());
if (user.isPresent()) {
var roles = user.get().getRoles().stream()
.map(role -> {
var auth = role.getRole();
return switch (role.getRole().scope()) {
case GLOBAL -> new AuthorityReference(auth);
case ORGANIZATION -> new AuthorityReference(auth,
role.getOrganization().getName());
case PROJECT -> new AuthorityReference(auth,
role.getProject().getProjectName());
};
})
.collect(Collectors.toSet());
token = token.withRoles(roles);
} else {
session.removeAttribute(TOKEN_ATTRIBUTE);
httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
httpResponse.setHeader(HttpHeaders.WWW_AUTHENTICATE, OAuth2AccessToken.BEARER_TYPE);
httpResponse.getOutputStream().println(
"{\"error\": \"" + "Unauthorized" + ",\n"
+ "\"status\": \"" + HttpServletResponse.SC_UNAUTHORIZED + ",\n"
+ "\"message\": \"User not found\",\n"
+ "\"path\": \"" + httpRequest.getRequestURI() + "\n"
+ "\"}");
return;
}
}

httpRequest.setAttribute(TOKEN_ATTRIBUTE, token);
RadarAuthentication authentication = new RadarAuthentication(new SessionRadarToken(token));
RadarAuthentication authentication = new RadarAuthentication(token);
authenticationManager.authenticate(authentication);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(httpRequest, httpResponse);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,12 @@ public class SessionRadarToken extends AbstractRadarToken implements Serializabl

/** Instantiate a serializable session token by copying an existing RadarToken. */
public SessionRadarToken(RadarToken token) {
this.roles = Set.copyOf(token.getRoles());
this(token, token.getRoles());
}

/** Instantiate a serializable session token by copying an existing RadarToken. */
private SessionRadarToken(RadarToken token, Set<AuthorityReference> roles) {
this.roles = Set.copyOf(roles);
this.subject = token.getSubject();
this.token = token.getToken();
this.scopes = List.copyOf(token.getScopes());
Expand Down Expand Up @@ -131,4 +136,20 @@ public List<String> getClaimList(String name) {
public String getUsername() {
return username;
}

public SessionRadarToken withRoles(Set<AuthorityReference> roles) {
return new SessionRadarToken(this, roles);
}

/**
* Create a new token.
* @return null if provided null, a session radar token otherwise.
*/
public static SessionRadarToken from(RadarToken token) {
if (token == null) {
return null;
} else {
return new SessionRadarToken(token);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ public OrganizationDTO save(OrganizationDTO organizationDto) {
*/
@Transactional(readOnly = true)
public List<OrganizationDTO> findAll() {
Stream<Organization> organizationsOfUser;
List<Organization> organizationsOfUser;

if (token.hasGlobalAuthorityForPermission(ORGANIZATION_READ)) {
organizationsOfUser = organizationRepository.findAll().stream();
organizationsOfUser = organizationRepository.findAll();
} else {
List<String> projectNames = token.getReferentsWithPermission(
RoleAuthority.Scope.PROJECT, ORGANIZATION_READ)
Expand All @@ -86,12 +86,11 @@ public List<OrganizationDTO> findAll() {
.filter(Objects::nonNull);

organizationsOfUser = Stream.concat(organizationsOfRole, organizationsOfProject)
.distinct();
.distinct()
.collect(Collectors.toList());
}

return organizationsOfUser
.map(organizationMapper::organizationToOrganizationDTO)
.collect(Collectors.toList());
return organizationMapper.organizationsToOrganizationDTOs(organizationsOfUser);
}

/**
Expand Down
19 changes: 12 additions & 7 deletions src/main/java/org/radarbase/management/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -282,23 +282,28 @@ public void updateUser(String userName, String firstName, String lastName,
* Update all information for a specific user, and return the modified user.
*
* @param userDto user to update
* @param updateProperties should update the user properties
* @return updated user
*/
@Transactional
public Optional<UserDTO> updateUser(UserDTO userDto) throws NotAuthorizedException {
public Optional<UserDTO> updateUser(
UserDTO userDto, boolean updateProperties) throws NotAuthorizedException {
Optional<User> userOpt = userRepository.findById(userDto.getId());
if (userOpt.isPresent()) {
User user = userOpt.get();
user.setLogin(userDto.getLogin());
user.setFirstName(userDto.getFirstName());
user.setLastName(userDto.getLastName());
user.setEmail(userDto.getEmail());
user.setActivated(userDto.isActivated());
user.setLangKey(userDto.getLangKey());
if (updateProperties) {
user.setLogin(userDto.getLogin());
user.setFirstName(userDto.getFirstName());
user.setLastName(userDto.getLastName());
user.setEmail(userDto.getEmail());
user.setActivated(userDto.isActivated());
user.setLangKey(userDto.getLangKey());
}
Set<Role> managedRoles = user.getRoles();
Set<Role> oldRoles = Set.copyOf(managedRoles);
managedRoles.clear();
managedRoles.addAll(getUserRoles(userDto.getRoles(), oldRoles));

user = userRepository.save(user);
log.debug("Changed Information for User: {}", user);
return Optional.of(userMapper.userToUserDTO(user));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.radarbase.management.service.dto;

import org.radarbase.auth.authorization.RoleAuthority;

public class AuthorityDTO {
private String name;
private String scope;

public AuthorityDTO() {
// POJO constructor
}

public AuthorityDTO(RoleAuthority role) {
this.name = role.authority();
this.scope = role.scope().name();
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public String getScope() {
return scope;
}

public void setScope(String scope) {
this.scope = scope;
}
}
Loading