diff --git a/README.md b/README.md index 43e8756f0f35b338d33c337fcffba4459b662815..f24392c925fd5d02f20bcdc7954c692b97853328 100644 --- a/README.md +++ b/README.md @@ -93,5 +93,5 @@ Afin d'essayer l'application, il faut pouvoir se connecter. Il faut pour cela cr INSERT INTO OreSiUser (id, login, password) values ('5a4dbd41-3fc9-4b3e-b593-a46bc888a7f9'::uuid, 'poussin', '$2a$12$4gAH34ZwgvgQNS0pbR5dGem1Nle0AT/.UwrZWfqtqMiJ0hXeYMvUG'); DROP ROLE IF EXISTS "5a4dbd41-3fc9-4b3e-b593-a46bc888a7f9"; CREATE ROLE "5a4dbd41-3fc9-4b3e-b593-a46bc888a7f9"; -GRANT "applicationCreator" TO "5a4dbd41-3fc9-4b3e-b593-a46bc888a7f9"; +GRANT "superadmin" TO "5a4dbd41-3fc9-4b3e-b593-a46bc888a7f9"; ``` \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/model/OreSiRoleForUser.java b/src/main/java/fr/inra/oresing/model/OreSiRoleForUser.java new file mode 100644 index 0000000000000000000000000000000000000000..82052b2704131a889d877f9aa99ef4d6796ed644 --- /dev/null +++ b/src/main/java/fr/inra/oresing/model/OreSiRoleForUser.java @@ -0,0 +1,10 @@ +package fr.inra.oresing.model; + +import lombok.Value; + +@Value +public class OreSiRoleForUser { + private String userId; + private String role; + private String applicationPattern; +} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/model/OreSiUser.java b/src/main/java/fr/inra/oresing/model/OreSiUser.java index a62fdc075214a31bcdea759b6f000b781f3d57c9..4227a92c241da62a5600f5e29dbddf306f225560 100644 --- a/src/main/java/fr/inra/oresing/model/OreSiUser.java +++ b/src/main/java/fr/inra/oresing/model/OreSiUser.java @@ -3,9 +3,13 @@ package fr.inra.oresing.model; import lombok.Getter; import lombok.Setter; +import java.util.LinkedList; +import java.util.List; + @Getter @Setter public class OreSiUser extends OreSiEntity { private String login; private String password; -} + private List<String> authorizations= new LinkedList<>(); +} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/persistence/AuthenticationService.java b/src/main/java/fr/inra/oresing/persistence/AuthenticationService.java index b7290852426351b1e6b30cfdcb4fc388b0bf04a7..8b2bbf38a621632e679b2bbee4727bb5c212e9ab 100644 --- a/src/main/java/fr/inra/oresing/persistence/AuthenticationService.java +++ b/src/main/java/fr/inra/oresing/persistence/AuthenticationService.java @@ -2,11 +2,9 @@ package fr.inra.oresing.persistence; import at.favre.lib.crypto.bcrypt.BCrypt; import com.google.common.base.Preconditions; +import com.google.common.base.Strings; import fr.inra.oresing.model.OreSiUser; -import fr.inra.oresing.persistence.roles.OreSiApplicationCreatorRole; -import fr.inra.oresing.persistence.roles.OreSiRole; -import fr.inra.oresing.persistence.roles.OreSiRoleToAccessDatabase; -import fr.inra.oresing.persistence.roles.OreSiUserRole; +import fr.inra.oresing.persistence.roles.*; import fr.inra.oresing.rest.CreateUserResult; import fr.inra.oresing.rest.LoginResult; import fr.inra.oresing.rest.OreSiApiRequestContext; @@ -17,6 +15,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.UUID; import java.util.function.Predicate; +import java.util.stream.Collectors; @Component @Transactional @@ -110,10 +109,102 @@ public class AuthenticationService { return new CreateUserResult(result.getId()); } - public void addUserRightCreateApplication(UUID userId) { + public OreSiUser deleteUserRightSuperadmin(UUID userId) { + resetRole(); + final OreSiUser oreSiUser = getOreSiUser(userId); OreSiUserRole roleToModify = getUserRole(userId); + OreSiSuperAdminRole roleToRevoke = OreSiRole.superAdmin(); + db.removeUserInRole(roleToModify, new OreSiRoleToBeGranted() { + @Override + public String getAsSqlRole() { + return OreSiSuperAdminRole.SUPER_ADMIN.getAsSqlRole(); + } + }); + return userRepository.findById(userId); + } + public OreSiUser addUserRightSuperadmin(UUID userId) { + resetRole(); + final OreSiUser oreSiUser = getOreSiUser(userId); + OreSiUserRole roleToModify = getUserRole(userId); + OreSiSuperAdminRole roleToAdd = OreSiRole.superAdmin(); + db.addUserInRole(roleToModify, new OreSiRoleToBeGranted() { + @Override + public String getAsSqlRole() { + return OreSiSuperAdminRole.SUPER_ADMIN.getAsSqlRole(); + } + }); + return userRepository.findById(userId); + } + + public OreSiUser deleteUserRightCreateApplication(UUID userId, String applicationPattern) { + resetRole(); + final OreSiUser oreSiUser = getOreSiUser(userId); + OreSiUserRole roleToModify = getUserRole(userId); + oreSiUser.getAuthorizations().remove(applicationPattern); + OreSiApplicationCreatorRole roleToAdd = OreSiRole.applicationCreator(); + db.removeUserInRole(roleToModify, roleToAdd); + final String expression = oreSiUser.getAuthorizations().stream() + .map(s -> String.format("%s", s)) + .collect(Collectors.joining("|", "name ~ '(", ")'")); + final SqlPolicy sqlPolicy = new SqlPolicy( + String.join("_", OreSiRole.applicationCreator().getAsSqlRole(), userId.toString()), + SqlSchema.main().application(), + SqlPolicy.PermissiveOrRestrictive.RESTRICTIVE, + SqlPolicy.Statement.ALL, + new OreSiRole() { + @Override + public String getAsSqlRole() { + return userId.toString(); + } + }, + expression + ); + if(oreSiUser.getAuthorizations().isEmpty()){ + db.dropPolicy(sqlPolicy); + }else{ + db.createPolicy(sqlPolicy); + } + + setRoleForClient(); + if(!Strings.isNullOrEmpty(applicationPattern)){ + userRepository.updateAuthorizations(userId, oreSiUser.getAuthorizations()); + userRepository.flush(); + } + resetRole(); + return userRepository.findById(userId); + } + + public OreSiUser addUserRightCreateApplication(UUID userId, String applicationPattern) { + resetRole(); + final OreSiUser oreSiUser = getOreSiUser(userId); + OreSiUserRole roleToModify = getUserRole(userId); + oreSiUser.getAuthorizations().add(applicationPattern); OreSiApplicationCreatorRole roleToAdd = OreSiRole.applicationCreator(); db.addUserInRole(roleToModify, roleToAdd); + final String expression = oreSiUser.getAuthorizations().stream() + .map(s -> String.format("%s", s)) + .collect(Collectors.joining("|", "name ~ '(", ")'")); + final SqlPolicy sqlPolicy = new SqlPolicy( + String.join("_", OreSiRole.applicationCreator().getAsSqlRole(), userId.toString()), + SqlSchema.main().application(), + SqlPolicy.PermissiveOrRestrictive.RESTRICTIVE, + SqlPolicy.Statement.ALL, + new OreSiRole() { + @Override + public String getAsSqlRole() { + return userId.toString(); + } + }, + expression + ); + db.createPolicy(sqlPolicy); + setRoleForClient(); + if(!Strings.isNullOrEmpty(applicationPattern)){ + userRepository.updateAuthorizations(userId, oreSiUser.getAuthorizations()); + userRepository.flush(); + } + resetRole(); + return userRepository.findById(userId); } public void removeUser(UUID userId) { @@ -135,6 +226,19 @@ public class AuthenticationService { return getUserRole(user); } + public boolean hasRole(OreSiRole role) { + setRoleForClient(); + final CurrentUserRoles currentUserRoles = userRepository.getRolesForCurrentUser(); + setRoleAdmin(); + return currentUserRoles.isSuper() || + currentUserRoles.getCurrentUser().equals(role.getAsSqlRole()) || + currentUserRoles.getMemberOf().contains(role.getAsSqlRole()); + } + + public boolean isSuperAdmin() { + return hasRole(OreSiRole.superAdmin()); + } + public OreSiUserRole getUserRole(OreSiUser user) { return OreSiUserRole.forUser(user); } diff --git a/src/main/java/fr/inra/oresing/persistence/JsonTableRepositoryTemplate.java b/src/main/java/fr/inra/oresing/persistence/JsonTableRepositoryTemplate.java index c48c72975479d035f037e760c947a05acfc6e8a0..4c3572c7f87ae1ea575aa03eeb123d2f0ce06f6d 100644 --- a/src/main/java/fr/inra/oresing/persistence/JsonTableRepositoryTemplate.java +++ b/src/main/java/fr/inra/oresing/persistence/JsonTableRepositoryTemplate.java @@ -43,15 +43,18 @@ abstract class JsonTableRepositoryTemplate<T extends OreSiEntity> implements Ini // 5min48 pour 100 // 5min50 pour 500 // 6min21 pour 1000 + final String s = namedParameterJdbcTemplate.queryForObject("select CURRENT_USER::TEXT;", Map.of(), String.class); return Iterators.partition(stream.iterator(), 50); } public List<UUID> storeAll(Stream<T> stream) { + final String s = namedParameterJdbcTemplate.queryForObject("select CURRENT_USER::TEXT;", Map.of(), String.class); String query = getUpsertQuery(); List<UUID> uuids = new LinkedList<>(); partition(stream).forEachRemaining(entities -> { entities.forEach(e -> { if (e.getId() == null) { + final String a = namedParameterJdbcTemplate.queryForObject("select login from oresiuser where id::TEXT=CURRENT_USER::TEXT;", Map.of(), String.class); e.setId(UUID.randomUUID()); } }); diff --git a/src/main/java/fr/inra/oresing/persistence/SqlService.java b/src/main/java/fr/inra/oresing/persistence/SqlService.java index 6bddf622013e4fed989bc1cb02ea6240e0cd22b8..c4b02d756039e8d3ca0201a3183e322082d51da3 100644 --- a/src/main/java/fr/inra/oresing/persistence/SqlService.java +++ b/src/main/java/fr/inra/oresing/persistence/SqlService.java @@ -64,7 +64,7 @@ public class SqlService { public void createPolicy(SqlPolicy sqlPolicy) { String createPolicySql = String.format( - "CREATE POLICY %s ON %s AS %s FOR %s TO %s USING (%s)", + "DROP POLICY IF EXISTS %1$s ON %2$s;CREATE POLICY %1$s ON %2$s AS %3$s FOR %4$s TO %5$s USING (%6$s);", sqlPolicy.getSqlIdentifier(), sqlPolicy.getTable().getSqlIdentifier(), sqlPolicy.getPermissiveOrRestrictive().name(), @@ -77,7 +77,7 @@ public class SqlService { public void dropPolicy(SqlPolicy sqlPolicy) { String createPolicySql = String.format( - "DROP POLICY %s ON %s", + "DROP POLICY IF EXISTS %s ON %s", sqlPolicy.getSqlIdentifier(), sqlPolicy.getTable().getSqlIdentifier() ); diff --git a/src/main/java/fr/inra/oresing/persistence/UserRepository.java b/src/main/java/fr/inra/oresing/persistence/UserRepository.java index dfdf47c77b353eb9fcf3ea142dbe81ec31f7ae52..aaae393b8aff9e0335db779ad50ad636cafe827e 100644 --- a/src/main/java/fr/inra/oresing/persistence/UserRepository.java +++ b/src/main/java/fr/inra/oresing/persistence/UserRepository.java @@ -2,18 +2,23 @@ package fr.inra.oresing.persistence; import com.google.common.collect.MoreCollectors; import fr.inra.oresing.model.OreSiUser; +import fr.inra.oresing.persistence.roles.CurrentUserRoles; +import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.stereotype.Component; -import java.util.Optional; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.*; +import java.util.stream.Collectors; @Component public class UserRepository extends JsonTableRepositoryTemplate<OreSiUser> { @Override protected String getUpsertQuery() { - return "INSERT INTO " + getTable().getSqlIdentifier() + " (id, login, password) SELECT id, login, password FROM json_populate_recordset(NULL::" + getTable().getSqlIdentifier() + ", :json::json)" - + " ON CONFLICT (id) DO UPDATE SET updateDate=current_timestamp, login=EXCLUDED.login, password=EXCLUDED.password" + return "INSERT INTO " + getTable().getSqlIdentifier() + " (id, login, password, authorizations) SELECT id, login, password, authorizations FROM json_populate_recordset(NULL::" + getTable().getSqlIdentifier() + ", :json::json)" + + " ON CONFLICT (id) DO UPDATE SET updateDate=current_timestamp, login=EXCLUDED.login, password=EXCLUDED.password, authorizations=EXCLUDED.authorizations" + " RETURNING id"; } @@ -33,4 +38,54 @@ public class UserRepository extends JsonTableRepositoryTemplate<OreSiUser> { new MapSqlParameterSource("login", login), getJsonRowMapper()).stream().collect(MoreCollectors.toOptional()); return result; } -} + + public CurrentUserRoles getRolesForRole(String role) { + String roleParam = role==null?"\"current_user\"()":String.format("\"%s\"", role); + final RowMapper<CurrentUserRoles> rowMapper = new RowMapper<>() { + + @Override + public CurrentUserRoles mapRow(ResultSet rs, int rowNum) throws SQLException { + String currentUser = rs.getString("currentUser"); + List<String> memberOf = Arrays.stream((String[])rs.getArray("memberOf").getArray()) + .collect(Collectors.toList()); + Boolean isSuper = rs.getBoolean("isSuper"); + return new CurrentUserRoles(currentUser, memberOf, isSuper); + } + }; + String query = "WITH RECURSIVE membership_tree(grpid, userid, isSuper) AS (\n" + + " SELECT r.oid, r.oid, r.rolsuper isSuper\n" + + " FROM pg_roles r\n" + + " UNION ALL\n" + + " SELECT m_1.roleid, t_1.userid, t_1.isSuper\n" + + " FROM pg_auth_members m_1, membership_tree t_1\n" + + " WHERE m_1.member = t_1.grpid\n" + + ")\n" + + "SELECT COALESCE(:roleName, CURRENT_USER) \"currentUser\",r.rolname AS usrname,t.isSuper \"isSuper\",\n" + + " array_agg(m.rolname) memberOf\n" + + "FROM membership_tree t, pg_roles r, pg_roles m\n" + + "WHERE t.grpid = m.oid AND t.userid = r.oid\n" + + "and COALESCE(:roleName, CURRENT_USER)=r.rolname\n" + + "group by userid, r.rolname,t.isSuper;"; + final Map<String, String> parameters =new HashMap<>(); + parameters.put("roleName", role); + return getNamedParameterJdbcTemplate().queryForObject( + query, + parameters, rowMapper); + + + } + public CurrentUserRoles getRolesForCurrentUser(){ + return getRolesForRole(null); + } + + public int updateAuthorizations(UUID userId, List<String> authorizations) { + String query = "update "+getTable().getSqlIdentifier()+" o\n" + + "set authorizations = :authorizations\n" + + "where id = :uuid::uuid\n"; + return getNamedParameterJdbcTemplate().update( + query, + new MapSqlParameterSource("authorizations", authorizations.toArray(String[]::new)) + .addValue("uuid",userId ) + ); + } +} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/persistence/roles/CurrentUserRoles.java b/src/main/java/fr/inra/oresing/persistence/roles/CurrentUserRoles.java new file mode 100644 index 0000000000000000000000000000000000000000..46ceefdf3df5f4d0c46b0a48e4999bd3fba4e25b --- /dev/null +++ b/src/main/java/fr/inra/oresing/persistence/roles/CurrentUserRoles.java @@ -0,0 +1,19 @@ +package fr.inra.oresing.persistence.roles; + +import lombok.Getter; + +import java.util.List; + +@Getter +public class CurrentUserRoles { + String currentUser; + List<String> memberOf; + + boolean isSuper; + + public CurrentUserRoles(String currentUser, List<String> memberOf, boolean isSuper) { + this.currentUser = currentUser; + this.memberOf = memberOf; + this.isSuper = isSuper; + } +} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/persistence/roles/OreSiRole.java b/src/main/java/fr/inra/oresing/persistence/roles/OreSiRole.java index c955582fa892fb104ca40eaf6c9e11d7a0782fe0..20fce679088e3dd26aacbfb9c229cf54529a7f50 100644 --- a/src/main/java/fr/inra/oresing/persistence/roles/OreSiRole.java +++ b/src/main/java/fr/inra/oresing/persistence/roles/OreSiRole.java @@ -2,7 +2,9 @@ package fr.inra.oresing.persistence.roles; import fr.inra.oresing.persistence.WithSqlIdentifier; -public interface OreSiRole extends WithSqlIdentifier { +@FunctionalInterface +public interface +OreSiRole extends WithSqlIdentifier { static OreSiAnonymousRole anonymous() { return OreSiAnonymousRole.ANONYMOUS; @@ -22,4 +24,4 @@ public interface OreSiRole extends WithSqlIdentifier { default String getSqlIdentifier() { return "\"" + getAsSqlRole() + "\""; } -} +} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/rest/AuthorizationRequest.java b/src/main/java/fr/inra/oresing/rest/AuthorizationRequest.java index b33be67ac449159a221ba68cdca3c27d408f4333..3e0a8d20ced9561e19731e19ea6a0d441983bc9b 100644 --- a/src/main/java/fr/inra/oresing/rest/AuthorizationRequest.java +++ b/src/main/java/fr/inra/oresing/rest/AuthorizationRequest.java @@ -9,5 +9,7 @@ public class AuthorizationRequest { String applicationNameOrId; + String dataType; + UUID authorizationId; -} +} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/rest/AuthorizationResources.java b/src/main/java/fr/inra/oresing/rest/AuthorizationResources.java index 374ea71e5803327e9f6dca60734e9a8cac5baa5d..9f345d462844825b5a9cdd7c494038cbc2f4ced1 100644 --- a/src/main/java/fr/inra/oresing/rest/AuthorizationResources.java +++ b/src/main/java/fr/inra/oresing/rest/AuthorizationResources.java @@ -2,6 +2,8 @@ package fr.inra.oresing.rest; import com.google.common.collect.ImmutableSet; import fr.inra.oresing.model.OreSiAuthorization; +import fr.inra.oresing.model.OreSiRoleForUser; +import fr.inra.oresing.model.OreSiUser; import fr.inra.oresing.persistence.roles.OreSiRightOnApplicationRole; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; @@ -28,22 +30,40 @@ public class AuthorizationResources { Set<UUID> previousUsers = authorization.getUuid()==null?new HashSet<>():authorization.getUsersId(); OreSiAuthorization oreSiAuthorization = authorizationService.addAuthorization(authorization); UUID authId = oreSiAuthorization.getId(); - OreSiRightOnApplicationRole roleForAuthorization = null; - if(authorization.getUuid()==null){ - roleForAuthorization = authorizationService.createRoleForAuthorization(authorization, oreSiAuthorization); - } - + OreSiRightOnApplicationRole roleForAuthorization = authorizationService.createRoleForAuthorization(authorization, oreSiAuthorization); authorizationService.updateRoleForManagement(previousUsers, oreSiAuthorization); String uri = UriUtils.encodePath("/applications/" + authorization.getApplicationNameOrId() + "/dataType/" + authorization.getDataType() + "/authorization/" + authId.toString(), Charset.defaultCharset()); return ResponseEntity.created(URI.create(uri)).body(Map.of("authorizationId", authId.toString())); } @GetMapping(value = "/applications/{nameOrId}/dataType/{dataType}/authorization/{authorizationId}", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity<GetAuthorizationResult> getAuthorization(@PathVariable("nameOrId") String applicationNameOrId, @PathVariable("authorizationId") UUID authorizationId) { - GetAuthorizationResult getAuthorizationResult = authorizationService.getAuthorization(new AuthorizationRequest(applicationNameOrId, authorizationId)); + public ResponseEntity<GetAuthorizationResult> getAuthorization(@PathVariable("nameOrId") String applicationNameOrId,@PathVariable("dataType") String dataType, @PathVariable("authorizationId") UUID authorizationId) { + GetAuthorizationResult getAuthorizationResult = authorizationService.getAuthorization(new AuthorizationRequest(applicationNameOrId,dataType, authorizationId)); return ResponseEntity.ok(getAuthorizationResult); } + @DeleteMapping(value = "/authorization/{role}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity<OreSiUser> deleteAuthorization( + @PathVariable(name = "role", required = true)String role, + @RequestParam(name = "userId", required = true)String userId, + @RequestParam(name = "applicationPattern", required = false)String applicationPattern) + throws NotSuperAdminException, NotApplicationCreatorRightsException { + final OreSiRoleForUser roleForUser = new OreSiRoleForUser(userId, role, applicationPattern); + OreSiUser user = authorizationService.deleteRoleUser(roleForUser); + return ResponseEntity.ok(user); + } + + @PutMapping(value = "/authorization/{role}", produces = MediaType.APPLICATION_JSON_VALUE) + public ResponseEntity<OreSiUser> addAuthorization( + @PathVariable(name = "role", required = true)String role, + @RequestParam(name = "userId", required = true)String userId, + @RequestParam(name = "applicationPattern", required = false)String applicationPattern) + throws NotSuperAdminException, NotApplicationCreatorRightsException { + final OreSiRoleForUser roleForUser = new OreSiRoleForUser(userId, role, applicationPattern); + OreSiUser user = authorizationService.addRoleUser(roleForUser); + return ResponseEntity.ok(user); + } + @GetMapping(value = "/applications/{nameOrId}/dataType/{dataType}/authorization", produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity<ImmutableSet<GetAuthorizationResult>> getAuthorizations(@PathVariable("nameOrId") String applicationNameOrId, @PathVariable("dataType") String dataType) { ImmutableSet<GetAuthorizationResult> getAuthorizationResults = authorizationService.getAuthorizations(applicationNameOrId, dataType); @@ -57,8 +77,8 @@ public class AuthorizationResources { } @DeleteMapping(value = "/applications/{nameOrId}/dataType/{dataType}/authorization/{authorizationId}", produces = MediaType.APPLICATION_JSON_VALUE) - public ResponseEntity<?> revokeAuthorization(@PathVariable("nameOrId") String applicationNameOrId, @PathVariable("authorizationId") UUID authorizationId) { - authorizationService.revoke(new AuthorizationRequest(applicationNameOrId, authorizationId)); + public ResponseEntity<?> revokeAuthorization(@PathVariable("nameOrId") String applicationNameOrId, @PathVariable("dataType") String dataType, @PathVariable("authorizationId") UUID authorizationId) { + authorizationService.revoke(new AuthorizationRequest(applicationNameOrId, dataType, authorizationId)); return ResponseEntity.noContent().build(); } } \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/rest/AuthorizationService.java b/src/main/java/fr/inra/oresing/rest/AuthorizationService.java index 9e3ebe6e26f1e803d56c2fdc9b84ae26b476c284..f13f3a7fd6e407f23c710cda8c76668afa0d323a 100644 --- a/src/main/java/fr/inra/oresing/rest/AuthorizationService.java +++ b/src/main/java/fr/inra/oresing/rest/AuthorizationService.java @@ -6,6 +6,7 @@ import fr.inra.oresing.checker.ReferenceLineChecker; import fr.inra.oresing.model.*; import fr.inra.oresing.persistence.*; import fr.inra.oresing.persistence.roles.OreSiRightOnApplicationRole; +import fr.inra.oresing.persistence.roles.OreSiRole; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -47,6 +48,17 @@ public class AuthorizationService { OreSiRightOnApplicationRole oreSiRightOnApplicationRole = OreSiRightOnApplicationRole.managementRole(application, modifiedAuthorization.getId()); db.addUserInRole(oreSiRightOnApplicationRole, OreSiRightOnApplicationRole.readerOn(application)); addOrRemoveAuthorizationForUsers(previousUsers, newUsers, oreSiRightOnApplicationRole); + + final String expression = String.format("name = '%s'",application.getName()); + final SqlPolicy sqlPolicy = new SqlPolicy( + String.join("_", "application","reader", oreSiRightOnApplicationRole.getAsSqlRole()), + SqlSchema.main().application(), + SqlPolicy.PermissiveOrRestrictive.PERMISSIVE, + SqlPolicy.Statement.SELECT, + oreSiRightOnApplicationRole, + expression + ); + db.createPolicy(sqlPolicy); if (modifiedAuthorization.getAuthorizations().containsKey(OperationType.publication)) { db.addUserInRole(oreSiRightOnApplicationRole, OreSiRightOnApplicationRole.writerOn(application)); SqlPolicy publishPolicy = toDatatypePolicy(modifiedAuthorization, oreSiRightOnApplicationRole, OperationType.publication, SqlPolicy.Statement.INSERT); @@ -91,7 +103,7 @@ public class AuthorizationService { authByType.forEach(authorization -> { authorization.getDataGroup() .forEach(datagroup -> Preconditions.checkArgument(authorizationDescription.getDataGroups().containsKey(datagroup))); - Set<String> labels =Optional.ofNullable( authorizationDescription) + Set<String> labels = Optional.ofNullable(authorizationDescription) .map(Configuration.AuthorizationDescription::getAuthorizationScopes) .map(Map::keySet) .orElseGet(Set::of); @@ -170,6 +182,15 @@ public class AuthorizationService { UUID authorizationId = revokeAuthorizationRequest.getAuthorizationId(); OreSiAuthorization oreSiAuthorization = authorizationRepository.findById(authorizationId); OreSiRightOnApplicationRole oreSiRightOnApplicationRole = OreSiRightOnApplicationRole.managementRole(application, authorizationId); + final SqlPolicy sqlPolicy = new SqlPolicy( + String.join("_", "application","reader", oreSiRightOnApplicationRole.getAsSqlRole()), + SqlSchema.main().application(), + null, + null, + null, + null + ); + db.dropPolicy(sqlPolicy); if (oreSiAuthorization.getAuthorizations().containsKey(OperationType.publication)) { db.addUserInRole(oreSiRightOnApplicationRole, OreSiRightOnApplicationRole.writerOn(application)); SqlPolicy publishPolicy = toDatatypePolicy(oreSiAuthorization, oreSiRightOnApplicationRole, OperationType.publication, SqlPolicy.Statement.INSERT); @@ -260,14 +281,14 @@ public class AuthorizationService { private ImmutableSortedSet<GetGrantableResult.DataGroup> getDataGroups(Application application, String dataType) { ImmutableSortedSet<GetGrantableResult.DataGroup> dataGroups = - Optional.of(application.getConfiguration().getDataTypes().get(dataType)) - .map(Configuration.DataTypeDescription::getAuthorization) - .map(Configuration.AuthorizationDescription::getDataGroups) - .map(dg -> dg.entrySet().stream() - .map(dataGroupEntry -> new GetGrantableResult.DataGroup(dataGroupEntry.getKey(), dataGroupEntry.getValue().getLabel())) - .collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.comparing(GetGrantableResult.DataGroup::getId)) ) - ) - .orElseGet(ImmutableSortedSet::of); + Optional.of(application.getConfiguration().getDataTypes().get(dataType)) + .map(Configuration.DataTypeDescription::getAuthorization) + .map(Configuration.AuthorizationDescription::getDataGroups) + .map(dg -> dg.entrySet().stream() + .map(dataGroupEntry -> new GetGrantableResult.DataGroup(dataGroupEntry.getKey(), dataGroupEntry.getValue().getLabel())) + .collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.comparing(GetGrantableResult.DataGroup::getId))) + ) + .orElseGet(ImmutableSortedSet::of); return dataGroups; } @@ -284,22 +305,22 @@ public class AuthorizationService { return Optional.of(application.getConfiguration().getDataTypes().get(dataType)) .map(Configuration.DataTypeDescription::getAuthorization) .map(Configuration.AuthorizationDescription::getAuthorizationScopes) - .map(authorizationScopes-> authorizationScopes.entrySet().stream() + .map(authorizationScopes -> authorizationScopes.entrySet().stream() .map( - authorizationScopeEntry -> { - String variable = authorizationScopeEntry.getValue().getVariable(); - String component = authorizationScopeEntry.getValue().getComponent(); - VariableComponentKey variableComponentKey = new VariableComponentKey(variable, component); - ReferenceLineChecker referenceLineChecker = referenceLineCheckers.get(variableComponentKey); - String lowestLevelReference = referenceLineChecker.getRefType(); - HierarchicalReferenceAsTree hierarchicalReferenceAsTree = oreSiService.getHierarchicalReferenceAsTree(application, lowestLevelReference); - ImmutableSortedSet<GetGrantableResult.AuthorizationScope.Option> rootOptions = hierarchicalReferenceAsTree.getRoots().stream() - .map(rootReferenceValue -> toOption(hierarchicalReferenceAsTree, rootReferenceValue)) - .collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.comparing(GetGrantableResult.AuthorizationScope.Option::getId))); - String authorizationScopeId = authorizationScopeEntry.getKey(); - return new GetGrantableResult.AuthorizationScope(authorizationScopeId, authorizationScopeId, rootOptions); - }) - .collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.comparing(GetGrantableResult.AuthorizationScope::getId))) + authorizationScopeEntry -> { + String variable = authorizationScopeEntry.getValue().getVariable(); + String component = authorizationScopeEntry.getValue().getComponent(); + VariableComponentKey variableComponentKey = new VariableComponentKey(variable, component); + ReferenceLineChecker referenceLineChecker = referenceLineCheckers.get(variableComponentKey); + String lowestLevelReference = referenceLineChecker.getRefType(); + HierarchicalReferenceAsTree hierarchicalReferenceAsTree = oreSiService.getHierarchicalReferenceAsTree(application, lowestLevelReference); + ImmutableSortedSet<GetGrantableResult.AuthorizationScope.Option> rootOptions = hierarchicalReferenceAsTree.getRoots().stream() + .map(rootReferenceValue -> toOption(hierarchicalReferenceAsTree, rootReferenceValue)) + .collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.comparing(GetGrantableResult.AuthorizationScope.Option::getId))); + String authorizationScopeId = authorizationScopeEntry.getKey(); + return new GetGrantableResult.AuthorizationScope(authorizationScopeId, authorizationScopeId, rootOptions); + }) + .collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.comparing(GetGrantableResult.AuthorizationScope::getId))) ) .orElseGet(ImmutableSortedSet::of); } @@ -310,4 +331,74 @@ public class AuthorizationService { .collect(ImmutableSortedSet.toImmutableSortedSet(Comparator.comparing(GetGrantableResult.AuthorizationScope.Option::getId))); return new GetGrantableResult.AuthorizationScope.Option(referenceValue.getHierarchicalKey().getSql(), referenceValue.getHierarchicalKey().getSql(), options); } + + public OreSiUserResult deleteRoleUser(OreSiRoleForUser roleForUser) { + if (OreSiRole.superAdmin().getAsSqlRole().equals(roleForUser.getRole())) { + return deleteAdminRoleUser(roleForUser); + } else if (OreSiRole.applicationCreator().getAsSqlRole().equals(roleForUser.getRole())) { + return deleteApplicationCreatorRoleUser(roleForUser); + } + throw new BadRoleException("cantDeleteRole", roleForUser.getRole()); + } + + private OreSiUserResult deleteApplicationCreatorRoleUser(OreSiRoleForUser oreSiUserRoleApplicationCreator) { + boolean canAddApplicationCreatorRole = canAddApplicationCreatorRole(oreSiUserRoleApplicationCreator); + if (canAddApplicationCreatorRole) { + final OreSiUser user = authenticationService.deleteUserRightCreateApplication(UUID.fromString(oreSiUserRoleApplicationCreator.getUserId()), oreSiUserRoleApplicationCreator.getApplicationPattern()); + return new OreSiUserResult(user, userRepository.getRolesForRole(oreSiUserRoleApplicationCreator.getUserId())); + } + throw new NotSuperAdminException(); + } + + private boolean canAddApplicationCreatorRole(OreSiRoleForUser oreSiUserRoleApplicationCreator) { + boolean canAddApplicationCreatorRole = false; + if (authenticationService.hasRole(OreSiRole.superAdmin())) { + canAddApplicationCreatorRole = true; + } else if (authenticationService.hasRole(OreSiRole.applicationCreator())) { + final OreSiUser user = userRepository.findById(UUID.fromString(oreSiUserRoleApplicationCreator.getUserId())); + if (user.getAuthorizations().contains(oreSiUserRoleApplicationCreator.getApplicationPattern())) { + canAddApplicationCreatorRole = true; + } else { + throw new NotApplicationCreatorRightsException(oreSiUserRoleApplicationCreator.getApplicationPattern(), user.getAuthorizations()); + } + + } + return canAddApplicationCreatorRole; + } + + private OreSiUserResult deleteAdminRoleUser(OreSiRoleForUser oreSiRoleForUserAdmin) { + boolean canAddsupeadmin = false; + if (authenticationService.hasRole(OreSiRole.superAdmin())) { + OreSiUser user = authenticationService.deleteUserRightSuperadmin(UUID.fromString(oreSiRoleForUserAdmin.getUserId())); + return new OreSiUserResult(user, userRepository.getRolesForRole(oreSiRoleForUserAdmin.getUserId())); + } + throw new NotSuperAdminException(); + } + + public OreSiUserResult addRoleUser(OreSiRoleForUser roleForUser) { + if (OreSiRole.superAdmin().getAsSqlRole().equals(roleForUser.getRole())) { + return addAdminRoleUser(roleForUser); + } else if (OreSiRole.applicationCreator().getAsSqlRole().equals(roleForUser.getRole())) { + return addApplicationCreatorRoleUser(roleForUser); + } + throw new BadRoleException("cantSetRole", roleForUser.getRole()); + } + + private OreSiUserResult addApplicationCreatorRoleUser(OreSiRoleForUser oreSiUserRoleApplicationCreator) { + boolean canAddApplicationCreatorRole = canAddApplicationCreatorRole(oreSiUserRoleApplicationCreator); + if (canAddApplicationCreatorRole) { + final OreSiUser user = authenticationService.addUserRightCreateApplication(UUID.fromString(oreSiUserRoleApplicationCreator.getUserId()), oreSiUserRoleApplicationCreator.getApplicationPattern()); + return new OreSiUserResult(user, userRepository.getRolesForRole(oreSiUserRoleApplicationCreator.getUserId())); + } + throw new NotSuperAdminException(); + } + + private OreSiUserResult addAdminRoleUser(OreSiRoleForUser oreSiRoleForUserAdmin) { + boolean canAddsupeadmin = false; + if (authenticationService.hasRole(OreSiRole.superAdmin())) { + OreSiUser user = authenticationService.addUserRightSuperadmin(UUID.fromString(oreSiRoleForUserAdmin.getUserId())); + return new OreSiUserResult(user, userRepository.getRolesForRole(oreSiRoleForUserAdmin.getUserId())); + } + throw new NotSuperAdminException(); + } } \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/rest/BadRoleException.java b/src/main/java/fr/inra/oresing/rest/BadRoleException.java new file mode 100644 index 0000000000000000000000000000000000000000..8f818b7528650f7f45b475a2d634f7fe22fba0cf --- /dev/null +++ b/src/main/java/fr/inra/oresing/rest/BadRoleException.java @@ -0,0 +1,19 @@ +package fr.inra.oresing.rest; + +import fr.inra.oresing.OreSiTechnicalException; +import lombok.Getter; + +@Getter +public class BadRoleException extends OreSiTechnicalException { + String role; + + public BadRoleException(String message, String role, Throwable cause) { + super(message, cause); + this.role = role; + } + + public BadRoleException(String message, String role) { + super(message); + this.role = role; + } +} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/rest/NotApplicationCreatorRightsException.java b/src/main/java/fr/inra/oresing/rest/NotApplicationCreatorRightsException.java new file mode 100644 index 0000000000000000000000000000000000000000..0f679dfab760f64d1307d216313812e45460be02 --- /dev/null +++ b/src/main/java/fr/inra/oresing/rest/NotApplicationCreatorRightsException.java @@ -0,0 +1,27 @@ +package fr.inra.oresing.rest; + +import fr.inra.oresing.OreSiTechnicalException; + +import java.util.List; + +public class NotApplicationCreatorRightsException extends OreSiTechnicalException { + public final static String NO_RIGHT_FOR_APPLICATION_CREATION = "NO_RIGHT_FOR_APPLICATION_CREATION"; + String applicationName; + List<String> applicationRestrictions; + public NotApplicationCreatorRightsException(String applicationName) { + super(NO_RIGHT_FOR_APPLICATION_CREATION); + this.applicationName = applicationName; + this.applicationRestrictions = List.of(); + } + public NotApplicationCreatorRightsException(String applicationName, List<String> applicationRestrictions) { + super(NO_RIGHT_FOR_APPLICATION_CREATION); + this.applicationName = applicationName; + this.applicationRestrictions = applicationRestrictions; + } + + public NotApplicationCreatorRightsException(Throwable cause) { + super(NO_RIGHT_FOR_APPLICATION_CREATION, cause); + this.applicationName = applicationName; + this.applicationRestrictions = List.of(); + } +} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/rest/NotSuperAdminException.java b/src/main/java/fr/inra/oresing/rest/NotSuperAdminException.java new file mode 100644 index 0000000000000000000000000000000000000000..cbe5d9015f4053dc7d7d0ee9d1e03a5894d12cfd --- /dev/null +++ b/src/main/java/fr/inra/oresing/rest/NotSuperAdminException.java @@ -0,0 +1,14 @@ +package fr.inra.oresing.rest; + +import fr.inra.oresing.OreSiTechnicalException; + +public class NotSuperAdminException extends OreSiTechnicalException { + public final static String SUPER_ADMIN_REQUIRED_FOR_OPERATION = "SUPER_ADMIN_REQUIRED_FOR_OPERATION"; + public NotSuperAdminException() { + super(SUPER_ADMIN_REQUIRED_FOR_OPERATION); + } + + public NotSuperAdminException(Throwable cause) { + super(SUPER_ADMIN_REQUIRED_FOR_OPERATION, cause); + } +} \ No newline at end of file diff --git a/src/main/java/fr/inra/oresing/rest/OreExceptionHandler.java b/src/main/java/fr/inra/oresing/rest/OreExceptionHandler.java index 2e1413f5324a527cb56d8cb2ef0e4426603c03d6..8a9fec6a3cc9a89521d77041384e84e9f836c96d 100644 --- a/src/main/java/fr/inra/oresing/rest/OreExceptionHandler.java +++ b/src/main/java/fr/inra/oresing/rest/OreExceptionHandler.java @@ -9,8 +9,13 @@ import org.postgresql.util.PSQLException; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.jdbc.BadSqlGrammarException; +import org.springframework.validation.ObjectError; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.bind.support.WebExchangeBindException; + +import java.util.List; import java.util.List; @@ -22,6 +27,12 @@ public class OreExceptionHandler { public ResponseEntity<String> handle(AuthenticationFailure eee) { return ResponseEntity.status(HttpStatus.FORBIDDEN).body(eee.getMessage()); } + @ExceptionHandler + @ResponseStatus(HttpStatus.BAD_REQUEST) + public List<ObjectError> exception(WebExchangeBindException ex) { + log.error("{}", ex.getLocalizedMessage(), ex); + return ex.getAllErrors(); + } @ExceptionHandler(value = BadSqlGrammarException.class) public ResponseEntity<String> handle(BadSqlGrammarException badSqlGrammarException) { diff --git a/src/main/java/fr/inra/oresing/rest/OreSiService.java b/src/main/java/fr/inra/oresing/rest/OreSiService.java index 3f6272b8620b8e8cc6ee6f96451531e294a64d81..124097dd4f76f5bbae158aee8d6e6478de2b584d 100644 --- a/src/main/java/fr/inra/oresing/rest/OreSiService.java +++ b/src/main/java/fr/inra/oresing/rest/OreSiService.java @@ -14,8 +14,7 @@ import fr.inra.oresing.groovy.StringGroovyExpression; import fr.inra.oresing.model.*; import fr.inra.oresing.model.chart.OreSiSynthesis; import fr.inra.oresing.persistence.*; -import fr.inra.oresing.persistence.roles.OreSiRightOnApplicationRole; -import fr.inra.oresing.persistence.roles.OreSiUserRole; +import fr.inra.oresing.persistence.roles.*; import fr.inra.oresing.rest.validationcheckresults.DateValidationCheckResult; import fr.inra.oresing.rest.validationcheckresults.DefaultValidationCheckResult; import fr.inra.oresing.rest.validationcheckresults.ReferenceValidationCheckResult; @@ -31,6 +30,7 @@ import org.flywaydb.core.Flyway; import org.flywaydb.core.api.Location; import org.flywaydb.core.api.configuration.ClassicConfiguration; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.BadSqlGrammarException; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; @@ -70,6 +70,13 @@ public class OreSiService { @Autowired private AuthenticationService authenticationService; + + @Autowired + private AuthorizationService authorizationService; + + @Autowired + private UserRepository userRepository; + @Autowired private CheckerFactory checkerFactory; @@ -120,6 +127,9 @@ public class OreSiService { } public UUID createApplication(String name, MultipartFile configurationFile, String comment) throws IOException, BadApplicationConfigurationException { + final OreSiUser currentUser = userRepository.findById(request.getRequestClient().getId()); + final boolean canCreateApplication = currentUser.getAuthorizations().stream() + .anyMatch(s -> name.matches(s)); Application app = new Application(); app.setName(name); app.setComment(comment); @@ -289,7 +299,20 @@ public class OreSiService { } private UUID changeApplicationConfiguration(Application app, MultipartFile configurationFile, Function<Application, Application> initApplication) throws IOException, BadApplicationConfigurationException { - + final String applicationName = app.getName(); + final OreSiUser currentUser = userRepository.findById(request.getRequestClient().getId()); + authenticationService.setRoleForClient(); + final boolean canCreateApplication = authenticationService.hasRole(OreSiRole.applicationCreator()) && currentUser.getAuthorizations().stream() + .anyMatch(s -> applicationName.matches(s)); + final boolean isSuperAdmin = authenticationService.isSuperAdmin(); + if (!(isSuperAdmin || canCreateApplication)) { + throw new NotApplicationCreatorRightsException(applicationName, currentUser.getAuthorizations()); + } else if (!isSuperAdmin) { + currentUser.getAuthorizations().stream() + .filter(s -> isSuperAdmin || applicationName.matches(s)) + .findAny() + .orElseThrow(() -> new NotApplicationCreatorRightsException(applicationName)); + } ConfigurationParsingResult configurationParsingResult; if (configurationFile.getOriginalFilename().matches(".*\\.zip")) { final byte[] bytes = new MultiYaml().parseConfigurationBytes(configurationFile); @@ -302,11 +325,15 @@ public class OreSiService { app.setReferenceType(new ArrayList<>(configuration.getReferences().keySet())); app.setDataType(new ArrayList<>(configuration.getDataTypes().keySet())); app.setConfiguration(configuration); - app = initApplication.apply(app); - UUID confId = storeFile(app, configurationFile, app.getComment()); - app.setConfigFile(confId); - UUID appId = repo.application().store(app); - return appId; + try { + app = initApplication.apply(app); + UUID confId = storeFile(app, configurationFile, app.getComment()); + app.setConfigFile(confId); + UUID appId = repo.application().store(app); + return appId; + } catch (BadSqlGrammarException bsge) { + throw new NotApplicationCreatorRightsException(applicationName, currentUser.getAuthorizations()); + } } public UUID addReference(Application app, String refType, MultipartFile file) throws IOException { @@ -569,13 +596,13 @@ public class OreSiService { final Configuration.AuthorizationDescription authorization = dataTypeDescription.getAuthorization(); final boolean haveAuthorizations = authorization != null; - final DateLineChecker timeScopeDateLineChecker = haveAuthorizations && authorization.getTimeScope()!=null? + final DateLineChecker timeScopeDateLineChecker = haveAuthorizations && authorization.getTimeScope() != null ? lineCheckers.stream() - .filter(lineChecker -> lineChecker instanceof DateLineChecker) - .map(lineChecker -> (DateLineChecker) lineChecker) - .filter(dateLineChecker -> dateLineChecker.getTarget().equals(authorization.getTimeScope())) - .collect(MoreCollectors.onlyElement()) - :null; + .filter(lineChecker -> lineChecker instanceof DateLineChecker) + .map(lineChecker -> (DateLineChecker) lineChecker) + .filter(dateLineChecker -> dateLineChecker.getTarget().equals(authorization.getTimeScope())) + .collect(MoreCollectors.onlyElement()) + : null; return rowWithData -> { Datum datum = Datum.copyOf(rowWithData.getDatum()); @@ -611,7 +638,7 @@ public class OreSiService { return Stream.empty(); } LocalDateTimeRange timeScope; - if (timeScopeDateLineChecker!=null) { + if (timeScopeDateLineChecker != null) { String timeScopeValue = datum.get(authorization.getTimeScope()); timeScope = LocalDateTimeRange.parse(timeScopeValue, timeScopeDateLineChecker); } else { @@ -619,12 +646,12 @@ public class OreSiService { } Map<String, Ltree> requiredAuthorizations = new LinkedHashMap<>(); - if(haveAuthorizations) { + if (haveAuthorizations) { authorization.getAuthorizationScopes().forEach((authorizationScope, authorizationScopeDescription) -> { VariableComponentKey variableComponentKey = authorizationScopeDescription.getVariableComponentKey(); String requiredAuthorization = datum.get(variableComponentKey); Ltree.checkSyntax(requiredAuthorization); - requiredAuthorizations.put(authorizationScope, Ltree.fromSql(requiredAuthorization)); + requiredAuthorizations.put(authorizationScope, Ltree.fromSql(requiredAuthorization)); }); } checkTimescopRangeInDatasetRange(timeScope, errors, binaryFileDataset, rowWithData.getLineNumber()); @@ -636,13 +663,13 @@ public class OreSiService { return Stream.of((Data) null); } LinkedHashMap<String, Configuration.DataGroupDescription> dataGroups; - if(!haveAuthorizations){ - dataGroups=new LinkedHashMap<>(); + if (!haveAuthorizations) { + dataGroups = new LinkedHashMap<>(); final Configuration.DataGroupDescription dataGroupDescription = new Configuration.DataGroupDescription(); dataGroupDescription.setData(dataTypeDescription.getData().keySet()); dataGroups.put("_default_", dataGroupDescription); - }else{ - dataGroups= authorization.getDataGroups(); + } else { + dataGroups = authorization.getDataGroups(); } Stream<Data> dataStream = dataGroups.entrySet().stream().map(entry -> { String dataGroup = entry.getKey(); diff --git a/src/main/java/fr/inra/oresing/rest/OreSiUserResult.java b/src/main/java/fr/inra/oresing/rest/OreSiUserResult.java new file mode 100644 index 0000000000000000000000000000000000000000..4581252c2d12d267853c6b3a100492a1df39d8eb --- /dev/null +++ b/src/main/java/fr/inra/oresing/rest/OreSiUserResult.java @@ -0,0 +1,21 @@ +package fr.inra.oresing.rest; + +import fr.inra.oresing.model.OreSiUser; +import fr.inra.oresing.persistence.roles.CurrentUserRoles; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class +OreSiUserResult extends OreSiUser { + private CurrentUserRoles roles; + + public OreSiUserResult(OreSiUser user, CurrentUserRoles userRoles) { + super(); + setLogin(user.getLogin()); + setAuthorizations(user.getAuthorizations()); + setId(user.getId()); + setRoles(userRoles); + } +} \ No newline at end of file diff --git a/src/main/resources/migration/main/V1__init_schema.sql b/src/main/resources/migration/main/V1__init_schema.sql index 0fc93b18c000b1cb714c4dcb06d1c3324fbe504e..550c3ee46fcad30765c8e38382348806fee894d4 100644 --- a/src/main/resources/migration/main/V1__init_schema.sql +++ b/src/main/resources/migration/main/V1__init_schema.sql @@ -2,7 +2,8 @@ CREATE EXTENSION IF NOT EXISTS "pgcrypto"; CREATE EXTENSION IF NOT EXISTS "ltree"; CREATE OR REPLACE FUNCTION fk_check(targetTable TEXT, uid UUID) -RETURNS BOOLEAN AS $$ + RETURNS BOOLEAN AS +$$ DECLARE result TEXT; BEGIN @@ -21,10 +22,11 @@ CREATE OR REPLACE FUNCTION public.jsonb_count_items(IN json jsonb) VOLATILE PARALLEL UNSAFE COST 100 - -AS $BODY$ -with elements as (select json->jsonb_object_keys(json) element) -select sum(jsonb_array_length(element)) from elements +AS +$BODY$ +with elements as (select json -> jsonb_object_keys(json) element) +select sum(jsonb_array_length(element)) +from elements $BODY$; /*-- check les foreign key pour le colonne references de la table data @@ -41,23 +43,26 @@ $$ language 'plpgsql';*/ --check if all elements of oreSiUser array are users CREATE OR REPLACE FUNCTION checks_users(users uuid[]) - RETURNS BOOLEAN AS $$ + RETURNS BOOLEAN AS +$$ DECLARE checked BOOLEAN; BEGIN select users <@ array_agg(id)::uuid[] into checked from OreSiUser OSU group by users; return checked; END; -$$ LANGUAGE plpgsql; +$$ LANGUAGE plpgsql; CREATE OR REPLACE FUNCTION name_check(application UUID, targetColumn TEXT, val TEXT) -RETURNS BOOLEAN AS $$ + RETURNS BOOLEAN AS +$$ DECLARE result TEXT; BEGIN - EXECUTE format('select count(id) > 0 from Application where id=$1 AND $2 = ANY (%s);', targetColumn) INTO result USING application, val; + EXECUTE format('select count(id) > 0 from Application where id=$1 AND $2 = ANY (%s);', + targetColumn) INTO result USING application, val; RETURN result; END; $$ language 'plpgsql'; @@ -67,24 +72,28 @@ create domain EntityRef as uuid NOT NULL; create domain ListEntityRef as uuid[] NOT NULL; create domain DateOrNow as timestamp DEFAULT current_timestamp; -create table OreSiUser ( - id EntityId PRIMARY KEY, - creationDate DateOrNow, - updateDate DateOrNow, - login Text UNIQUE NOT NULL, - password text NOT NULL +create table OreSiUser +( + id EntityId PRIMARY KEY, + creationDate DateOrNow, + updateDate DateOrNow, + login Text UNIQUE NOT NULL, + password text NOT NULL,-- can be null + authorizations TEXT[] ); -create table Application ( - id EntityId PRIMARY KEY, - creationDate DateOrNow, - updateDate DateOrNow, - name Text, - comment TEXT NOT NULL, +create table Application +( + id EntityId PRIMARY KEY, + creator name default current_user, + creationDate DateOrNow, + updateDate DateOrNow, + name Text, + comment TEXT NOT NULL, referenceType TEXT[], -- liste des types de references existantes - dataType TEXT[], -- liste des types de data existants + dataType TEXT[], -- liste des types de data existants configuration jsonb, -- le fichier de configuration sous forme json - configFile uuid CHECK(fk_check(name || '.BinaryFile', configFile))-- can be null + configFile uuid CHECK (fk_check(name || '.BinaryFile', configFile)) ); CREATE INDEX application_referenceType_gin_idx ON application USING gin (referenceType); @@ -102,41 +111,46 @@ CREATE ROLE "applicationCreator"; GRANT INSERT, UPDATE ON Application TO "applicationCreator"; -GRANT SELECT, UPDATE, DELETE, REFERENCES ON Application TO public; +GRANT SELECT ON Application TO public ; -ALTER TABLE Application ENABLE ROW LEVEL SECURITY; +GRANT SELECT , UPDATE , DELETE ON OreSiUser TO "superadmin", "applicationCreator"; -CREATE POLICY "applicationCreator_Application_insert" ON Application AS PERMISSIVE - FOR INSERT TO "applicationCreator" - WITH CHECK ( true ); +GRANT SELECT, UPDATE, DELETE, REFERENCES ON Application TO "applicationCreator",superadmin; + +ALTER TABLE Application + ENABLE ROW LEVEL SECURITY; +CREATE POLICY "superadmin_Application_insert" + ON Application AS PERMISSIVE + TO superadmin + using (true) + with check (true); -CREATE POLICY "applicationCreator_Application_select" ON Application AS PERMISSIVE - FOR SELECT TO "applicationCreator" - USING ( true ); CREATE AGGREGATE jsonb_object_agg(jsonb) (SFUNC = 'jsonb_concat', STYPE = jsonb, INITCOND = '{}'); CREATE AGGREGATE aggregate_by_array_concatenation(anyarray) (SFUNC = 'array_cat', STYPE = anyarray, INITCOND = '{}'); -create type COMPOSITE_DATE as ( - datetimestamp "timestamp", - formattedDate "varchar" -) ; +create type COMPOSITE_DATE as +( + datetimestamp "timestamp", + formattedDate "varchar" +); CREATE FUNCTION castTextToCompositeDate(Text) RETURNS COMPOSITE_DATE AS - 'select - (substring($1 from 6 for 19)::timestamp, +'select (substring($1 from 6 for 19)::timestamp, substring($1 from 26))::COMPOSITE_DATE;' LANGUAGE SQL IMMUTABLE RETURNS NULL ON NULL INPUT; CREATE CAST (TEXT AS COMPOSITE_DATE) WITH FUNCTION castTextToCompositeDate(Text) AS ASSIGNMENT; CREATE FUNCTION castCompositeDateToTimestamp(COMPOSITE_DATE) RETURNS TIMESTAMP -AS 'select ($1).datetimestamp;' +AS +'select ($1).datetimestamp;' LANGUAGE SQL IMMUTABLE RETURNS NULL ON NULL INPUT; CREATE CAST (COMPOSITE_DATE AS TIMESTAMP) WITH FUNCTION castCompositeDateToTimestamp(COMPOSITE_DATE) AS ASSIGNMENT; CREATE FUNCTION castCompositeDateToFormattedDate(COMPOSITE_DATE) RETURNS Text -AS 'select ($1).formattedDate;' +AS +'select ($1).formattedDate;' LANGUAGE SQL IMMUTABLE RETURNS NULL ON NULL INPUT; diff --git a/src/test/java/fr/inra/oresing/rest/AuthorizationResourcesTest.java b/src/test/java/fr/inra/oresing/rest/AuthorizationResourcesTest.java index 8f6eb12e09747ebf8a6248d8be5df138c311dfea..971c5ad5b1689f17934fe6eb27ef53b0c64b4f25 100644 --- a/src/test/java/fr/inra/oresing/rest/AuthorizationResourcesTest.java +++ b/src/test/java/fr/inra/oresing/rest/AuthorizationResourcesTest.java @@ -5,6 +5,9 @@ import fr.inra.oresing.OreSiNg; import fr.inra.oresing.persistence.AuthenticationService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.assertj.core.util.Strings; +import org.hamcrest.Matchers; +import org.hamcrest.core.IsEqual; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -14,15 +17,19 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestExecutionListeners; import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.support.DirtiesContextTestExecutionListener; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.transaction.annotation.Transactional; import javax.servlet.http.Cookie; +import java.util.Map; import static org.hamcrest.Matchers.*; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; @@ -45,31 +52,35 @@ public class AuthorizationResourcesTest { @Autowired private AuthenticationService authenticationService; + + @Autowired + private NamedParameterJdbcTemplate namedParameterJdbcTemplate; + @Autowired private Fixtures fixtures; @Test public void testAddAuthorization() throws Exception { Cookie authCookie = fixtures.addApplicationAcbb(); - CreateUserResult createUserResult = authenticationService.createUser("UnReader" , "xxxxxxxx"); + CreateUserResult createUserResult = authenticationService.createUser("UnReader", "xxxxxxxx"); String readerUserId = createUserResult.getUserId().toString(); Cookie authReaderCookie = mockMvc.perform(post("/api/v1/login") - .param("login" , "UnReader") - .param("password" , "xxxxxxxx")) + .param("login", "UnReader") + .param("password", "xxxxxxxx")) .andReturn().getResponse().getCookie(AuthHelper.JWT_COOKIE_NAME); { String response = mockMvc.perform(get("/api/v1/applications") .cookie(authCookie) ).andReturn().getResponse().getContentAsString(); - Assert.assertTrue("Le créateur de l'application doit pouvoir la retrouver dans la liste" , response.contains("acbb")); + Assert.assertTrue("Le créateur de l'application doit pouvoir la retrouver dans la liste", response.contains("acbb")); } { String response = mockMvc.perform(get("/api/v1/applications") .cookie(authReaderCookie) ).andReturn().getResponse().getContentAsString(); - Assert.assertFalse("On ne devrait pas voir l'application car les droits n'ont pas encore été accordés" , response.contains("acbb")); + Assert.assertFalse("On ne devrait pas voir l'application car les droits n'ont pas encore été accordés", response.contains("acbb")); } { @@ -89,7 +100,7 @@ public class AuthorizationResourcesTest { { String json = "{\n" + - " \"usersId\":[\""+readerUserId+"\"],\n" + + " \"usersId\":[\"" + readerUserId + "\"],\n" + " \"applicationNameOrId\":\"acbb\",\n" + " \"id\": null,\n" + " \"name\": \"une authorization sur acbb\",\n" + @@ -134,7 +145,7 @@ public class AuthorizationResourcesTest { String response = mockMvc.perform(get("/api/v1/applications") .cookie(authReaderCookie) ).andReturn().getResponse().getContentAsString(); - Assert.assertTrue("Une fois l'accès donné, on doit pouvoir avec l'application dans la liste" , response.contains("acbb")); + Assert.assertTrue("Une fois l'accès donné, on doit pouvoir avec l'application dans la liste", response.contains("acbb")); } { @@ -153,14 +164,13 @@ public class AuthorizationResourcesTest { @Test public void testAddAuthorizationOnTwoScopes() throws Exception { - Cookie authCookie = fixtures.addApplicationHauteFrequence(); - CreateUserResult createUserResult = authenticationService.createUser("UnReader" , "xxxxxxxx"); + CreateUserResult createUserResult = authenticationService.createUser("UnReader", "xxxxxxxx"); String readerUserId = createUserResult.getUserId().toString(); Cookie authReaderCookie = mockMvc.perform(post("/api/v1/login") - .param("login" , "UnReader") - .param("password" , "xxxxxxxx")) + .param("login", "UnReader") + .param("password", "xxxxxxxx")) .andReturn().getResponse().getCookie(AuthHelper.JWT_COOKIE_NAME); String authorizationId; @@ -168,7 +178,7 @@ public class AuthorizationResourcesTest { { String json = "{\n" + - " \"usersId\":[\""+readerUserId+"\"],\n" + + " \"usersId\":[\"" + readerUserId + "\"],\n" + " \"applicationNameOrId\":\"hautefrequence\",\n" + " \"id\": null,\n" + " \"name\": \"une authorization sur haute fréquence\",\n" + @@ -214,8 +224,7 @@ public class AuthorizationResourcesTest { { String json = mockMvc.perform(get("/api/v1/applications/hautefrequence/dataType/hautefrequence/authorization/" + authorizationId) - .cookie(authCookie) - .accept(MediaType.APPLICATION_JSON)) + .cookie(authCookie)) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); @@ -235,7 +244,7 @@ public class AuthorizationResourcesTest { .andExpect(jsonPath("$.rows[*].values.localization.projet").value(not(hasItemInArray(equalTo("rnt"))), String[].class)) .andExpect(jsonPath("$.rows[*].values.date.day").value(hasItemInArray(equalTo("date:2016-06-14T00:00:00:14/06/2016")), String[].class)) .andExpect(jsonPath("$.rows[*].values.date.day").value(not(hasItemInArray(equalTo("date:2017-01-30T00:00:00:30/01/2017"))), String[].class)) - .andExpect(jsonPath("$.totalRows" , equalTo(7456))) + .andExpect(jsonPath("$.totalRows", equalTo(7456))) .andReturn().getResponse().getContentAsString(); @@ -257,8 +266,105 @@ public class AuthorizationResourcesTest { .cookie(authReaderCookie) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.totalRows" , equalTo(-1))) + .andExpect(jsonPath("$.totalRows", equalTo(-1))) .andReturn().getResponse().getContentAsString(); } } + + @Test + public void testAddApplicationMonsoere() throws Exception { + fixtures.addMonsoreApplication(); + } + + @Test + public void testAddRightForAddApplication() throws Exception { + + { + final String TEST = "test"; + CreateUserResult dbUserResult = authenticationService.createUser(TEST, TEST); + final Cookie dbUserCookies = mockMvc.perform(post("/api/v1/login") + .param("login", TEST) + .param("password", TEST)) + .andReturn().getResponse().getCookie(AuthHelper.JWT_COOKIE_NAME); + addRoleAdmin(dbUserResult); + String applicationCreatorLogin = "applicationCreator"; + String applicationCreatorPassword = "xxxxxxxx"; + CreateUserResult applicationCreatorResult = authenticationService.createUser(applicationCreatorLogin, applicationCreatorPassword); + final Cookie applicationCreatorCookies = mockMvc.perform(post("/api/v1/login") + .param("login", applicationCreatorLogin) + .param("password", applicationCreatorPassword)) + .andReturn().getResponse().getCookie(AuthHelper.JWT_COOKIE_NAME); + String lambdaLogin = "lambda"; + String lambdaPassword = "xxxxxxxx"; + CreateUserResult lambdaResult = authenticationService.createUser(lambdaLogin, lambdaPassword); + final Cookie lambdaCookie = mockMvc.perform(post("/api/v1/login") + .param("login", lambdaLogin) + .param("password", lambdaPassword)) + .andReturn().getResponse().getCookie(AuthHelper.JWT_COOKIE_NAME); + + { + //l'administrateur peut créer des applications. + final String monsoreResult = fixtures.createApplicationMonSore(dbUserCookies, "monsore"); + Assert.assertFalse(Strings.isNullOrEmpty(JsonPath.parse(monsoreResult).read("$.id", String.class))); + } + { + // on donne les droits pour un pattern acbb + + ResultActions resultActions = mockMvc.perform(put("/api/v1/authorization/applicationCreator") + .param("userId", applicationCreatorResult.getUserId().toString()) + .param("applicationPattern", "acbb") + .cookie(dbUserCookies)) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$.roles.currentUser", IsEqual.equalTo(applicationCreatorResult.getUserId().toString()))) + .andExpect(jsonPath("$.roles.memberOf", Matchers.hasItem("applicationCreator"))) + .andExpect(jsonPath("$.authorizations", Matchers.hasItem("acbb"))) + .andExpect(jsonPath("$.id", IsEqual.equalTo(applicationCreatorResult.getUserId().toString()))); + + //on peut déposer acbb + final String acbbResult = fixtures.createApplicationMonSore(applicationCreatorCookies, "acbb"); + Assert.assertFalse(Strings.isNullOrEmpty(JsonPath.parse(acbbResult).read("$.id", String.class))); + //on ne peut déposer monsore + Assert.assertEquals("NO_RIGHT_FOR_APPLICATION_CREATION", fixtures.createApplicationMonSore(applicationCreatorCookies, "monsore")); + + } + { + //on donne des droits pour le pattern monsore + ResultActions resultActions = mockMvc.perform(put("/api/v1/authorization/applicationCreator") + .param("userId", applicationCreatorResult.getUserId().toString()) + .param("applicationPattern", "monsore") + .cookie(dbUserCookies)) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$.roles.currentUser", IsEqual.equalTo(applicationCreatorResult.getUserId().toString()))) + .andExpect(jsonPath("$.roles.memberOf", Matchers.hasItem("applicationCreator"))) + .andExpect(jsonPath("$.authorizations", Matchers.hasItem("monsore"))) + .andExpect(jsonPath("$.id", IsEqual.equalTo(applicationCreatorResult.getUserId().toString()))); + + //on peut déposer monsore + final String acbbResult = fixtures.createApplicationMonSore(applicationCreatorCookies, "acbb"); + Assert.assertFalse(Strings.isNullOrEmpty(JsonPath.parse(acbbResult).read("$.id", String.class))); + + } + { + //on supprime des droits pour le pattern monsore + ResultActions resultActions = mockMvc.perform(delete("/api/v1/authorization/applicationCreator") + .param("userId", applicationCreatorResult.getUserId().toString()) + .param("applicationPattern", "monsore") + .cookie(dbUserCookies)) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$.roles.currentUser", IsEqual.equalTo(applicationCreatorResult.getUserId().toString()))) + .andExpect(jsonPath("$.roles.memberOf", not(Matchers.hasItem("applicationCreator")))) + .andExpect(jsonPath("$.authorizations", not(Matchers.hasItem("monsore")))) + .andExpect(jsonPath("$.id", IsEqual.equalTo(applicationCreatorResult.getUserId().toString()))); + + //on ne peut déposer monsore + Assert.assertEquals("NO_RIGHT_FOR_APPLICATION_CREATION", fixtures.createApplicationMonSore(applicationCreatorCookies, "monsore")); + } + } + + } + + @Transactional + void addRoleAdmin(CreateUserResult dbUserResult) { + namedParameterJdbcTemplate.update("grant \"superadmin\" to \"" + dbUserResult.getUserId().toString() + "\"", Map.of()); + } } \ No newline at end of file diff --git a/src/test/java/fr/inra/oresing/rest/Fixtures.java b/src/test/java/fr/inra/oresing/rest/Fixtures.java index b2dfa7a657c201a2062b90ed7e2772a21c855753..6e112612acd40f4afcae62eaaa31fe79633cadb9 100644 --- a/src/test/java/fr/inra/oresing/rest/Fixtures.java +++ b/src/test/java/fr/inra/oresing/rest/Fixtures.java @@ -5,14 +5,24 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.io.Resources; import fr.inra.oresing.OreSiTechnicalException; +import fr.inra.oresing.model.OreSiUser; import fr.inra.oresing.persistence.AuthenticationService; +import fr.inra.oresing.persistence.UserRepository; import org.apache.commons.io.IOUtils; +import org.hamcrest.Matchers; +import org.hamcrest.core.IsEqual; +import org.junit.Assert; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.mock.web.MockHttpServletResponse; import org.springframework.mock.web.MockMultipartFile; import org.springframework.stereotype.Component; import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.util.NestedServletException; import javax.servlet.http.Cookie; import java.io.IOException; @@ -24,11 +34,14 @@ import java.time.temporal.ChronoUnit; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; +import java.util.UUID; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @Component @@ -39,7 +52,11 @@ public class Fixtures { @Autowired private AuthenticationService authenticationService; + @Autowired + private NamedParameterJdbcTemplate namedParameterJdbcTemplate; + @Autowired + private UserRepository userRepository; private Cookie cookie; public String getMonsoreApplicationName() { @@ -82,12 +99,12 @@ public class Fixtures { //fougeres-fou_4_swc_j_01-01-1999_31-01-1999.csv final Pattern pattern = Pattern.compile("(.*)_" + datatype + "_(.*)_(.*).csv"); final Matcher matcher = pattern.matcher(fileName); - if(!matcher.matches()){ + if (!matcher.matches()) { return null; } String zone_etude = matcher.group(1); final String[] parent_site = zone_etude.split("-"); - if(parent_site.length>1){ + if (parent_site.length > 1) { zone_etude = String.format("%1$s.%1$s__%2$s", parent_site[0], parent_site[1]); } final DateTimeFormatter formaterIn = DateTimeFormatter.ofPattern("dd-MM-yyyy"); @@ -95,8 +112,8 @@ public class Fixtures { final boolean isMonthly = datatype.matches(".*_m"); final String format = (isMonthly ? "01-" : "") + "%s"; - String dateDebut =formaterOut.format(LocalDate.parse(String.format(format, matcher.group(2)), formaterIn).atStartOfDay(ZoneOffset.UTC))+" 00:00:00"; - String dateFin =formaterOut.format(LocalDate.parse(String.format(format, matcher.group(3)), formaterIn).atTime(0,0).plus(1, isMonthly ? ChronoUnit.MONTHS:ChronoUnit.DAYS))+" 00:00:00"; + String dateDebut = formaterOut.format(LocalDate.parse(String.format(format, matcher.group(2)), formaterIn).atStartOfDay(ZoneOffset.UTC)) + " 00:00:00"; + String dateFin = formaterOut.format(LocalDate.parse(String.format(format, matcher.group(3)), formaterIn).atTime(0, 0).plus(1, isMonthly ? ChronoUnit.MONTHS : ChronoUnit.DAYS)) + " 00:00:00"; return String.format("{\n" + " \"fileid\":null,\n" + " \"binaryfiledataset\":{\n" + @@ -249,12 +266,12 @@ public class Fixtures { return "/data/migration/couleurs.csv"; } - private Cookie addApplicationCreatorUser() throws Exception { + public Cookie addSuperAdmin(String applicationPattern) throws Exception { if (cookie == null) { String aPassword = "xxxxxxxx"; - String aLogin = "poussin"; + String aLogin = "superAdmin"; CreateUserResult createUserResult = authenticationService.createUser(aLogin, aPassword); - authenticationService.addUserRightCreateApplication(createUserResult.getUserId()); + authenticationService.addUserRightCreateApplication(createUserResult.getUserId(), applicationPattern); cookie = mockMvc.perform(post("/api/v1/login") .param("login", aLogin) .param("password", aPassword)) @@ -263,15 +280,68 @@ public class Fixtures { return cookie; } - public Cookie addMonsoreApplication() throws Exception { - Cookie authCookie = addApplicationCreatorUser(); + @Transactional + void addRoleAdmin(CreateUserResult dbUserResult) { + namedParameterJdbcTemplate.update("grant \"superadmin\" to \"" + dbUserResult.getUserId().toString() + "\"", Map.of()); + } + + public Cookie addApplicationCreatorUser(String applicationPattern) throws Exception { + if (cookie == null) { + String aPassword = "xxxxxxxx"; + String aLogin = "poussin"; + CreateUserResult createUserResult = authenticationService.createUser(aLogin, aPassword); + addRoleAdmin(createUserResult); + final MockHttpServletResponse response = mockMvc.perform(post("/api/v1/login") + .param("login", aLogin) + .param("password", aPassword)) + .andReturn().getResponse(); + cookie = response.getCookie(AuthHelper.JWT_COOKIE_NAME); + } + String aPassword = "xxxxxxxx"; + String aLogin = applicationPattern; + CreateUserResult createUserResult = authenticationService.createUser(aLogin, aPassword); + final UUID userId = createUserResult.getUserId(); + ResultActions resultActions = mockMvc.perform(put("/api/v1/authorization/applicationCreator") + .param("userId", userId.toString()) + .param("applicationPattern", applicationPattern) + .cookie(cookie)) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$.roles.currentUser", IsEqual.equalTo(userId.toString()))) + .andExpect(jsonPath("$.roles.memberOf", Matchers.hasItem("applicationCreator"))) + .andExpect(jsonPath("$.authorizations", Matchers.hasItem(applicationPattern))) + .andExpect(jsonPath("$.id", IsEqual.equalTo(userId.toString()))); + final OreSiUser user = userRepository.findById(userId); + Assert.assertTrue(user.getAuthorizations().contains(applicationPattern)); + Cookie applicationCreator = mockMvc.perform(post("/api/v1/login") + .param("login", aLogin) + .param("password", aPassword)) + .andReturn().getResponse().getCookie(AuthHelper.JWT_COOKIE_NAME); + return applicationCreator; + } + + public String createApplicationMonSore(Cookie authCookie, String applicationName) throws Exception { + ResultActions resultActions = null; try (InputStream configurationFile = getClass().getResourceAsStream(getMonsoreApplicationConfigurationResourceName())) { MockMultipartFile configuration = new MockMultipartFile("file", "monsore.yaml", "text/plain", configurationFile); - mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/monsore") - .file(configuration) - .cookie(authCookie)) - .andExpect(MockMvcResultMatchers.status().isCreated()); + resultActions = mockMvc.perform(MockMvcRequestBuilders.multipart(String.format("/api/v1/applications/%s", applicationName == null ? "monsore" : applicationName)) + .file(configuration) + .cookie(authCookie)); + return resultActions.andExpect(MockMvcResultMatchers.status().isCreated()) + .andReturn() + .getResponse().getContentAsString(); + } catch (NestedServletException e) { + if (e.getCause() instanceof NotApplicationCreatorRightsException) { + throw (NotApplicationCreatorRightsException) e.getCause(); + } + throw e; + } catch (AssertionError e) { + return resultActions.andReturn().getResolvedException().getMessage(); } + } + + public Cookie addMonsoreApplication() throws Exception { + Cookie authCookie = addApplicationCreatorUser("monsore"); + String result = createApplicationMonSore(authCookie, "monsore"); // Ajout de referentiel for (Map.Entry<String, String> e : getMonsoreReferentielFiles().entrySet()) { @@ -296,7 +366,7 @@ public class Fixtures { } public Cookie addMigrationApplication() throws Exception { - Cookie authCookie = addApplicationCreatorUser(); + Cookie authCookie = addApplicationCreatorUser("fakeapp"); try (InputStream configurationFile = getClass().getResourceAsStream(getMigrationApplicationConfigurationResourceName(1))) { MockMultipartFile configuration = new MockMultipartFile("file", "fake-app.yaml", "text/plain", configurationFile); mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/fakeapp") @@ -327,7 +397,7 @@ public class Fixtures { } public Cookie addApplicationAcbb() throws Exception { - Cookie authCookie = addApplicationCreatorUser(); + Cookie authCookie = addApplicationCreatorUser("acbb"); try (InputStream configurationFile = getClass().getResourceAsStream(getAcbbApplicationConfigurationResourceName())) { MockMultipartFile configuration = new MockMultipartFile("file", "acbb.yaml", "text/plain", configurationFile); mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/acbb") @@ -399,7 +469,7 @@ public class Fixtures { } public Cookie addApplicationHauteFrequence() throws Exception { - Cookie authCookie = addApplicationCreatorUser(); + Cookie authCookie = addApplicationCreatorUser("hautefrequence"); try (InputStream configurationFile = getClass().getResourceAsStream(getHauteFrequenceApplicationConfigurationResourceName())) { MockMultipartFile configuration = new MockMultipartFile("file", "hautefrequence.yaml", "text/plain", configurationFile); mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/hautefrequence") @@ -468,7 +538,7 @@ public class Fixtures { } public Cookie addApplicationOLAC() throws Exception { - Cookie authCookie = addApplicationCreatorUser(); + Cookie authCookie = addApplicationCreatorUser("olac"); try (InputStream configurationFile = getClass().getResourceAsStream(getOlaApplicationConfigurationResourceName())) { MockMultipartFile configuration = new MockMultipartFile("file", "olac.yaml", "text/plain", configurationFile); mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/olac") @@ -629,7 +699,7 @@ public class Fixtures { } public Cookie addApplicationFORET() throws Exception { - Cookie authCookie = addApplicationCreatorUser(); + Cookie authCookie = addApplicationCreatorUser("foret"); try (InputStream configurationFile = getClass().getResourceAsStream(getForetApplicationConfigurationResourceName())) { MockMultipartFile configuration = new MockMultipartFile("file", "foret.yaml", "text/plain", configurationFile); mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/foret") @@ -700,7 +770,7 @@ public class Fixtures { } public void addApplicationRecursivity() throws Exception { - Cookie authCookie = addApplicationCreatorUser(); + Cookie authCookie = addApplicationCreatorUser("recursivite"); try (InputStream in = getClass().getResourceAsStream(getRecursivityApplicationConfigurationResourceName())) { MockMultipartFile configuration = new MockMultipartFile("file", "recursivity.yaml", "text/plain", in); mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/recursivite") diff --git a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java index a52761084da178b0301b97ea355dd30a804d979f..a9abeb742d79e7a6a58c3c2adfae53724861ff41 100644 --- a/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java +++ b/src/test/java/fr/inra/oresing/rest/OreSiResourcesTest.java @@ -10,6 +10,7 @@ import fr.inra.oresing.ValidationLevel; import fr.inra.oresing.checker.InvalidDatasetContentException; import fr.inra.oresing.persistence.AuthenticationService; import fr.inra.oresing.persistence.JsonRowMapper; +import fr.inra.oresing.persistence.UserRepository; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; @@ -31,6 +32,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMock import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureWebMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; +import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.mock.web.MockMultipartFile; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.TestExecutionListeners; @@ -42,6 +44,7 @@ import org.springframework.test.web.servlet.ResultActions; import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.util.NestedServletException; import javax.servlet.http.Cookie; @@ -54,9 +57,9 @@ import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.hamcrest.Matchers.*; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItemInArray; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @RunWith(SpringRunner.class) @@ -88,14 +91,27 @@ public class OreSiResourcesTest { private UUID userId; private CreateUserResult lambdaUser; + @Autowired + private NamedParameterJdbcTemplate namedParameterJdbcTemplate; + @Autowired + private UserRepository userRepository; + + @Before public void createUser() throws Exception { - userId = authenticationService.createUser("poussin", "xxxxxxxx").getUserId(); + final CreateUserResult authUser = authenticationService.createUser("poussin", "xxxxxxxx"); + userId = authUser.getUserId(); lambdaUser = authenticationService.createUser("lambda", "xxxxxxxx"); authCookie = mockMvc.perform(post("/api/v1/login") .param("login", "poussin") .param("password", "xxxxxxxx")) .andReturn().getResponse().getCookie(AuthHelper.JWT_COOKIE_NAME); + addRoleAdmin(authUser); + } + + @Transactional + void addRoleAdmin(CreateUserResult dbUserResult) { + namedParameterJdbcTemplate.update("grant \"superadmin\" to \"" + dbUserResult.getUserId().toString() + "\"", Map.of()); } @Test @@ -103,21 +119,31 @@ public class OreSiResourcesTest { public void addApplicationMonsore() throws Exception { String appId; + final CreateUserResult monsoereUser = authenticationService.createUser("monsore", "xxxxxxxx"); + UUID monsoreUserId = monsoereUser.getUserId(); + Cookie monsoreCookie = mockMvc.perform(post("/api/v1/login") + .param("login", "monsore") + .param("password", "xxxxxxxx")) + .andReturn().getResponse().getCookie(AuthHelper.JWT_COOKIE_NAME); + URL resource = getClass().getResource(fixtures.getMonsoreApplicationConfigurationResourceName()); try (InputStream in = Objects.requireNonNull(resource).openStream()) { MockMultipartFile configuration = new MockMultipartFile("file", "monsore.yaml", "text/plain", in); // on n'a pas le droit de creer de nouvelle application - mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/monsore") - .file(configuration) - .cookie(authCookie)) - .andExpect(status().is4xxClientError()); - authenticationService.addUserRightCreateApplication(userId); + final NotApplicationCreatorRightsException resolvedException = (NotApplicationCreatorRightsException) mockMvc.perform(multipart("/api/v1/applications/monsore") + .file(configuration) + .cookie(monsoreCookie)) + .andExpect(status().is4xxClientError()) + .andReturn().getResolvedException(); + addUserRightCreateApplication(monsoreUserId, "monsore"); + Assert.assertEquals("monsore", resolvedException.applicationName); + addUserRightCreateApplication(monsoreUserId, "monsore"); String response = mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/monsore") .file(configuration) .param("comment", "commentaire") - .cookie(authCookie)) + .cookie(monsoreCookie)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.id", IsNull.notNullValue())) .andReturn().getResponse().getContentAsString(); @@ -127,7 +153,7 @@ public class OreSiResourcesTest { String response = mockMvc.perform(get("/api/v1/applications/{appId}", appId) .contentType(MediaType.APPLICATION_JSON) - .cookie(authCookie)) + .cookie(monsoreCookie)) .andExpect(status().isOk()) .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) // id @@ -148,7 +174,7 @@ public class OreSiResourcesTest { response = mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/monsore/references/{refType}", e.getKey()) .file(refFile) - .cookie(authCookie)) + .cookie(monsoreCookie)) .andExpect(status().isCreated()) .andExpect(jsonPath("$.id", IsNull.notNullValue())) .andReturn().getResponse().getContentAsString(); @@ -158,13 +184,13 @@ public class OreSiResourcesTest { } mockMvc.perform(get("/api/v1/applications/{appId}", appId) .contentType(MediaType.APPLICATION_JSON) - .cookie(authCookie)) + .cookie(monsoreCookie)) .andExpect(status().isOk()) .andExpect(jsonPath("$.referenceSynthesis[ ?(@.referenceType=='valeurs_qualitatives')].lineCount", IsEqual.equalTo(List.of(3)))); String getReferencesResponse = mockMvc.perform(get("/api/v1/applications/monsore/references/sites") .contentType(MediaType.APPLICATION_JSON) - .cookie(authCookie)) + .cookie(monsoreCookie)) .andExpect(status().isOk()) .andExpect(content().contentTypeCompatibleWith(MediaType.APPLICATION_JSON)) .andReturn().getResponse().getContentAsString(); @@ -179,7 +205,7 @@ public class OreSiResourcesTest { response = mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/monsore/data/pem") .file(refFile) - .cookie(authCookie)) + .cookie(monsoreCookie)) .andExpect(status().is2xxSuccessful()) .andReturn().getResponse().getContentAsString(); @@ -193,7 +219,7 @@ public class OreSiResourcesTest { MockMultipartFile refFile = new MockMultipartFile("file", "data-pem.csv", "text/plain", bytes); response = mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/monsore/data/pem") .file(refFile) - .cookie(authCookie)) + .cookie(monsoreCookie)) .andExpect(status().isBadRequest()) .andReturn().getResponse().getContentAsString(); log.debug(StringUtils.abbreviate(response, 50)); @@ -203,7 +229,7 @@ public class OreSiResourcesTest { // list des types de data response = mockMvc.perform(get("/api/v1/applications/monsore/data") - .cookie(authCookie)) + .cookie(monsoreCookie)) .andExpect(status().isOk()) .andReturn().getResponse().getContentAsString(); @@ -232,7 +258,7 @@ public class OreSiResourcesTest { } String actualJson = mockMvc.perform(get("/api/v1/applications/monsore/data/pem") - .cookie(authCookie) + .cookie(monsoreCookie) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.variables").isArray()) @@ -266,7 +292,7 @@ public class OreSiResourcesTest { String filter = "{\"application\":null,\"applicationNameOrId\":null,\"dataType\":null,\"offset\":null,\"limit\":15,\"variableComponentSelects\":[],\"variableComponentFilters\":[{\"variableComponentKey\":{\"variable\":\"date\",\"component\":\"value\"},\"filter\":null,\"type\":\"date\",\"format\":\"dd/MM/yyyy\",\"intervalValues\":{\"from\":\"1984-01-01\",\"to\":\"1984-01-01\"}},{\"variableComponentKey\":{\"variable\":\"Nombre d'individus\",\"component\":\"value\"},\"filter\":null,\"type\":\"numeric\",\"format\":\"integer\",\"intervalValues\":{\"from\":\"20\",\"to\":\"29\"}},{\"variableComponentKey\":{\"variable\":\"Couleur des individus\",\"component\":\"value\"},\"filter\":\"vert\",\"type\":\"reference\",\"format\":\"uuid\",\"intervalValues\":null}],\"variableComponentOrderBy\":[{\"variableComponentKey\":{\"variable\":\"site\",\"component\":\"plateforme\"},\"order\":\"ASC\",\"type\":null,\"format\":null}]}"; Resources.toString(Objects.requireNonNull(getClass().getResource("/data/monsore/compare/export.json")), Charsets.UTF_8); String actualJson = mockMvc.perform(get("/api/v1/applications/monsore/data/pem") - .cookie(authCookie) + .cookie(monsoreCookie) .param("downloadDatasetQuery", filter) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) @@ -284,7 +310,7 @@ public class OreSiResourcesTest { { String expectedCsv = Resources.toString(Objects.requireNonNull(getClass().getResource("/data/monsore/compare/export.csv")), Charsets.UTF_8); String actualCsv = mockMvc.perform(get("/api/v1/applications/monsore/data/pem") - .cookie(authCookie) + .cookie(monsoreCookie) .accept(MediaType.TEXT_PLAIN)) .andExpect(status().isOk()) // .andExpect(content().string(expectedCsv)) @@ -308,7 +334,7 @@ public class OreSiResourcesTest { MockMultipartFile refFile = new MockMultipartFile("file", "data-pem.csv", "text/plain", invalidCsv.getBytes(StandardCharsets.UTF_8)); response = mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/monsore/data/pem") .file(refFile) - .cookie(authCookie)) + .cookie(monsoreCookie)) .andExpect(status().is4xxClientError()) .andReturn().getResponse().getContentAsString(); log.debug(StringUtils.abbreviate(response, 50)); @@ -408,7 +434,7 @@ public class OreSiResourcesTest { try (InputStream in = Objects.requireNonNull(resource).openStream()) { MockMultipartFile configuration = new MockMultipartFile("file", "monsore.yaml", "text/plain", in); //définition de l'application - authenticationService.addUserRightCreateApplication(userId); + addUserRightCreateApplication(userId, "monsore"); String response = mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/monsore") .file(configuration) @@ -597,12 +623,12 @@ public class OreSiResourcesTest { */ @Test public void testProgressiveYamlWithoutAuthorization() throws Exception { - + String authorizationId; URL resource = getClass().getResource(fixtures.getProgressiveYaml().get("yamlWithoutAuthorization")); try (InputStream in = Objects.requireNonNull(resource).openStream()) { MockMultipartFile configuration = new MockMultipartFile("file", "progressive.yaml", "text/plain", in); //définition de l'application - authenticationService.addUserRightCreateApplication(userId); + addUserRightCreateApplication(userId, "progressive"); String result = mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/progressive") .file(configuration) @@ -614,7 +640,7 @@ public class OreSiResourcesTest { progressiveYamlAddData(); String lambdaUserId = lambdaUser.getUserId().toString(); - Cookie authReaderCookie = mockMvc.perform(post("/api/v1/login") + Cookie readerCookies = mockMvc.perform(post("/api/v1/login") .param("login", "lambda") .param("password", "xxxxxxxx")) .andReturn().getResponse().getCookie(AuthHelper.JWT_COOKIE_NAME); @@ -622,14 +648,14 @@ public class OreSiResourcesTest { { String response = mockMvc.perform(get("/api/v1/applications") - .cookie(authReaderCookie) + .cookie(readerCookies) ).andReturn().getResponse().getContentAsString(); Assert.assertFalse("On ne devrait pas voir l'application car les droits n'ont pas encore été accordés", response.contains("progressive")); } { mockMvc.perform(get("/api/v1/applications/progressive/data/date_de_visite") - .cookie(authReaderCookie) + .cookie(readerCookies) .accept(MediaType.TEXT_PLAIN)) .andExpect(status().is4xxClientError()); } @@ -666,31 +692,59 @@ public class OreSiResourcesTest { "}\n" + "}"; + // L'utilisateur sans droit ne peut voir les applications + String response = mockMvc.perform(get("/api/v1/applications") + .cookie(readerCookies) + ) + .andExpect(jsonPath("$[*].name", Matchers.hasSize(0))) + .andReturn().getResponse().getContentAsString(); + + MockHttpServletRequestBuilder create = post("/api/v1/applications/progressive/dataType/date_de_visite/authorization") .contentType(MediaType.APPLICATION_JSON) .cookie(authCookie) .content(json); - String response = mockMvc.perform(create) + response = mockMvc.perform(create) .andExpect(status().isCreated()) .andReturn().getResponse().getContentAsString(); + authorizationId = JsonPath.parse(response).read("$.authorizationId", String.class); log.debug(StringUtils.abbreviate(response, 50)); } { + // Une fois l'accès donné, on doit pouvoir avec l'application dans la liste" String response = mockMvc.perform(get("/api/v1/applications") - .cookie(authReaderCookie) - ).andReturn().getResponse().getContentAsString(); - Assert.assertTrue("Une fois l'accès donné, on doit pouvoir avec l'application dans la liste", response.contains("progressive")); + .cookie(readerCookies) + ) + .andExpect(jsonPath("$[*].name", Matchers.hasSize(1))) + .andExpect(jsonPath("$[*].name", Matchers.contains("progressive"))) + .andReturn().getResponse().getContentAsString(); } { String json = mockMvc.perform(get("/api/v1/applications/progressive/data/date_de_visite") - .cookie(authReaderCookie) + .cookie(readerCookies) .accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) .andExpect(jsonPath("$.rows[*].values.relevant.numero").value(hasItemInArray(equalTo("125")), String[].class)) .andReturn().getResponse().getContentAsString(); } + MockHttpServletRequestBuilder delete = delete(String.format("/api/v1/applications/progressive/dataType/date_de_visite/authorization/%s", authorizationId)) + .contentType(MediaType.APPLICATION_JSON) + .cookie(authCookie); + mockMvc.perform(delete) + .andExpect(status().is2xxSuccessful()) + .andReturn().getResponse().getContentAsString(); + // L'utilisateur sans droit ne peut voir les applications + + //TODO + + /*String response = mockMvc.perform(get("/api/v1/applications") + .cookie(readerCookies) + ) + .andExpect(jsonPath("$[*].name", Matchers.hasSize(0))) + .andReturn().getResponse().getContentAsString();*/ + } @@ -701,7 +755,7 @@ public class OreSiResourcesTest { try (InputStream in = Objects.requireNonNull(resource).openStream()) { MockMultipartFile configuration = new MockMultipartFile("file", "progressive.yaml", "text/plain", in); //définition de l'application - authenticationService.addUserRightCreateApplication(userId); + addUserRightCreateApplication(userId, "progressive"); String result = mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/progressive") .file(configuration) @@ -727,7 +781,7 @@ public class OreSiResourcesTest { try (InputStream in = Objects.requireNonNull(resource).openStream()) { MockMultipartFile configuration = new MockMultipartFile("file", "progressive.yaml", "text/plain", in); //définition de l'application - authenticationService.addUserRightCreateApplication(userId); + addUserRightCreateApplication(userId, "progressive"); BadApplicationConfigurationException exception = (BadApplicationConfigurationException) mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/progressive") .file(configuration) @@ -753,7 +807,7 @@ public class OreSiResourcesTest { try (InputStream in = Objects.requireNonNull(resource).openStream()) { MockMultipartFile configuration = new MockMultipartFile("file", "progressive.yaml", "text/plain", in); //définition de l'application - authenticationService.addUserRightCreateApplication(userId); + addUserRightCreateApplication(userId, "progressive"); final ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/progressive") .file(configuration) @@ -772,7 +826,7 @@ public class OreSiResourcesTest { try (InputStream in = Objects.requireNonNull(resource).openStream()) { MockMultipartFile configuration = new MockMultipartFile("file", "progressive.yaml", "text/plain", in); //définition de l'application - authenticationService.addUserRightCreateApplication(userId); + addUserRightCreateApplication(userId, "progressive"); final ResultActions resultActions = mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/progressive") .file(configuration) @@ -796,7 +850,7 @@ public class OreSiResourcesTest { try (InputStream in = Objects.requireNonNull(resource).openStream()) { MockMultipartFile configuration = new MockMultipartFile("file", "progressive.yaml", "text/plain", in); //définition de l'application - authenticationService.addUserRightCreateApplication(userId); + addUserRightCreateApplication(userId, "progressive"); String response = mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/progressive") .file(configuration) @@ -851,7 +905,7 @@ public class OreSiResourcesTest { try (InputStream in = Objects.requireNonNull(resource).openStream()) { MockMultipartFile configuration = new MockMultipartFile("file", "recursivity.yaml", "text/plain", in); //définition de l'application - authenticationService.addUserRightCreateApplication(userId); + addUserRightCreateApplication(userId, "recursivite"); String response = mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/recursivite") .file(configuration) @@ -867,9 +921,9 @@ public class OreSiResourcesTest { .cookie(authCookie)) .andExpect(status().is2xxSuccessful()) .andExpect(jsonPath("$.references.taxon.dynamicColumns['propriétés de taxons'].reference", IsEqual.equalTo("proprietes_taxon"))) - .andExpect(jsonPath("$.references.taxon.dynamicColumns['propriétés de taxons'].headerPrefix", IsEqual.equalTo("pt_"))) - .andExpect(jsonPath("$.internationalization.references.taxon.internationalizedDynamicColumns['propriétés de taxons'].en", IsEqual.equalTo("Properties of Taxa"))) - .andReturn().getResponse().getContentAsString(); + .andExpect(jsonPath("$.references.taxon.dynamicColumns['propriétés de taxons'].headerPrefix", IsEqual.equalTo("pt_"))) + .andExpect(jsonPath("$.internationalization.references.taxon.internationalizedDynamicColumns['propriétés de taxons'].en", IsEqual.equalTo("Properties of Taxa"))) + .andReturn().getResponse().getContentAsString(); } @@ -928,8 +982,7 @@ public class OreSiResourcesTest { @Test @Category(ACBB_TEST.class) public void addApplicationAcbb() throws Exception { - authenticationService.addUserRightCreateApplication(userId); - + addUserRightCreateApplication(userId, "acbb"); URL resource = getClass().getResource(fixtures.getAcbbApplicationConfigurationResourceName()); assert resource != null; try (InputStream in = resource.openStream()) { @@ -951,6 +1004,18 @@ public class OreSiResourcesTest { addDataSWC(); } + private void addUserRightCreateApplication(UUID userId, String pattern) throws Exception { + ResultActions resultActions = mockMvc.perform(put("/api/v1/authorization/applicationCreator") + .param("userId", userId.toString()) + .param("applicationPattern", pattern) + .cookie(authCookie)) + .andExpect(status().is2xxSuccessful()) + .andExpect(jsonPath("$.roles.currentUser", IsEqual.equalTo(userId.toString()))) + .andExpect(jsonPath("$.roles.memberOf", Matchers.hasItem("applicationCreator"))) + .andExpect(jsonPath("$.authorizations", Matchers.hasItem(pattern))) + .andExpect(jsonPath("$.id", IsEqual.equalTo(userId.toString()))); + } + private void addDataSWC() throws Exception { try (InputStream in = fixtures.openSwcDataResourceName(true)) { MockMultipartFile file = new MockMultipartFile("file", "SWC.csv", "text/plain", in); @@ -1106,7 +1171,7 @@ public class OreSiResourcesTest { @Test @Category(HAUTE_FREQUENCE_TEST.class) public void addApplicationHauteFrequence() throws Exception { - authenticationService.addUserRightCreateApplication(userId); + addUserRightCreateApplication(userId, "hautefrequence"); try (InputStream configurationFile = fixtures.getClass().getResourceAsStream(fixtures.getHauteFrequenceApplicationConfigurationResourceName())) { MockMultipartFile configuration = new MockMultipartFile("file", "hautefrequence.yaml", "text/plain", configurationFile); mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/hautefrequence") @@ -1139,7 +1204,7 @@ public class OreSiResourcesTest { @Test @Category(OTHERS_TEST.class) public void addDuplicatedTest() throws Exception { - authenticationService.addUserRightCreateApplication(userId); + addUserRightCreateApplication(userId, "duplicated"); try (InputStream configurationFile = fixtures.getClass().getResourceAsStream(fixtures.getDuplicatedApplicationConfigurationResourceName())) { MockMultipartFile configuration = new MockMultipartFile("file", "duplicated.yaml", "text/plain", configurationFile); mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/duplicated") @@ -1394,7 +1459,7 @@ on test le dépôt d'un fichier récursif @Test @Category(OTHERS_TEST.class) public void addApplicationOLAC() throws Exception { - authenticationService.addUserRightCreateApplication(userId); + addUserRightCreateApplication(userId, "olac"); try (InputStream configurationFile = fixtures.getClass().getResourceAsStream(fixtures.getOlaApplicationConfigurationResourceName())) { MockMultipartFile configuration = new MockMultipartFile("file", "olac.yaml", "text/plain", configurationFile); mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/olac") @@ -1482,7 +1547,7 @@ on test le dépôt d'un fichier récursif @Test @Category(OTHERS_TEST.class) public void addApplicationFORET_essai() throws Exception { - authenticationService.addUserRightCreateApplication(userId); + addUserRightCreateApplication(userId, "foret"); try (InputStream configurationFile = fixtures.getClass().getResourceAsStream(fixtures.getForetEssaiApplicationConfigurationResourceName())) { MockMultipartFile configuration = new MockMultipartFile("file", "foret_essai.yaml", "text/plain", configurationFile); mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/foret") @@ -1514,7 +1579,7 @@ on test le dépôt d'un fichier récursif if (entry.getKey().equals("swc_j")) { authenticationService.setRoleAdmin(); - final String responseForBuildSynthesis = mockMvc.perform(MockMvcRequestBuilders.put("/api/v1/applications/foret/synthesis/{refType}", entry.getKey()) + final String responseForBuildSynthesis = mockMvc.perform(put("/api/v1/applications/foret/synthesis/{refType}", entry.getKey()) .cookie(authCookie)) .andExpect(jsonPath("$.SWC", Matchers.hasSize(8))) .andReturn().getResponse().getContentAsString(); @@ -1531,7 +1596,7 @@ on test le dépôt d'un fichier récursif @Test @Category(OTHERS_TEST.class) public void addApplicationFORET() throws Exception { - authenticationService.addUserRightCreateApplication(userId); + addUserRightCreateApplication(userId, "foret"); try (InputStream configurationFile = fixtures.getClass().getResourceAsStream(fixtures.getForetApplicationConfigurationResourceName())) { MockMultipartFile configuration = new MockMultipartFile("file", "foret.yaml", "text/plain", configurationFile); mockMvc.perform(MockMvcRequestBuilders.multipart("/api/v1/applications/foret")