Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Migration to role group #71

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/main/java/cz/cvut/kbss/study/config/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package cz.cvut.kbss.study.config;

import cz.cvut.kbss.study.exception.RecordManagerException;
import cz.cvut.kbss.study.model.Role;
import cz.cvut.kbss.study.security.CsrfHeaderFilter;
import cz.cvut.kbss.study.security.CustomSwitchUserFilter;
import cz.cvut.kbss.study.security.SecurityConstants;
Expand Down Expand Up @@ -79,7 +80,7 @@ public SecurityFilterChain filterChain(HttpSecurity http, ConfigReader config,
LOG.debug("Using internal security mechanisms.");
final AuthenticationManager authManager = buildAuthenticationManager(http);
http.authorizeHttpRequests(
(auth) -> auth.requestMatchers("/rest/users/impersonate").hasAuthority(SecurityConstants.ROLE_ADMIN)
(auth) -> auth.requestMatchers("/rest/users/impersonate").hasAuthority(Role.administrator.getRoleName())
.anyRequest().permitAll())
.cors((auth) -> auth.configurationSource(corsConfigurationSource(config)))
.csrf(AbstractHttpConfigurer::disable)
Expand Down
128 changes: 128 additions & 0 deletions src/main/java/cz/cvut/kbss/study/model/Role.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package cz.cvut.kbss.study.model;

import com.fasterxml.jackson.annotation.JsonValue;
import cz.cvut.kbss.jopa.model.annotations.Individual;
import cz.cvut.kbss.study.security.SecurityConstants;
import java.util.Optional;

public enum Role {

// TODO deprecated -- should be removed.
@Individual(iri = Vocabulary.s_i_RM_ADMIN)
administrator(SecurityConstants.ROLE_ADMIN, Vocabulary.s_i_RM_ADMIN),
// TODO deprecated -- should be removed.
@Individual(iri = Vocabulary.s_i_RM_USER)
user(SecurityConstants.ROLE_USER, Vocabulary.s_i_RM_USER),

@Individual(iri = Vocabulary.s_i_impersonate_role)
impersonate(SecurityConstants.impersonate, Vocabulary.s_i_impersonate_role),

@Individual(iri = Vocabulary.s_i_delete_all_records_role)
deleteAllRecords(SecurityConstants.deleteAllRecords, Vocabulary.s_i_delete_all_records_role),

@Individual(iri = Vocabulary.s_i_view_all_records_role)
viewAllRecords(SecurityConstants.viewAllRecords, Vocabulary.s_i_view_all_records_role),

@Individual(iri = Vocabulary.s_i_edit_all_records_role)
editAllRecords(SecurityConstants.editAllRecords, Vocabulary.s_i_edit_all_records_role),

@Individual(iri = Vocabulary.s_i_delete_organization_records_role)
deleteOrganizationRecords(SecurityConstants.deleteOrganizationRecords, Vocabulary.s_i_delete_organization_records_role),

@Individual(iri = Vocabulary.s_i_view_organization_records_role)
viewOrganizationRecords(SecurityConstants.viewOrganizationRecords, Vocabulary.s_i_view_organization_records_role),

@Individual(iri = Vocabulary.s_i_edit_organization_records_role)
editOrganizationRecords(SecurityConstants.editOrganizationRecords, Vocabulary.s_i_edit_organization_records_role),

@Individual(iri = Vocabulary.s_i_edit_users_role)
editUsers(SecurityConstants.editUsers, Vocabulary.s_i_edit_users_role),

@Individual(iri = Vocabulary.s_i_complete_records_role)
completeRecords(SecurityConstants.completeRecords, Vocabulary.s_i_complete_records_role),

@Individual(iri = Vocabulary.s_i_reject_records_role)
rejectRecords(SecurityConstants.rejectRecords, Vocabulary.s_i_reject_records_role),

@Individual(iri = Vocabulary.s_i_publish_records_role)
publishRecords(SecurityConstants.publishRecords, Vocabulary.s_i_publish_records_role),

@Individual(iri = Vocabulary.s_i_import_codelists_role)
importCodelists(SecurityConstants.importCodelists, Vocabulary.s_i_import_codelists_role);

private final String iri;

public final String roleName;

Role(String roleName, String iri) {
this.iri = iri;
this.roleName = roleName;
}

@JsonValue
public String getRoleName() {
return roleName;
}

public String getIri() {
return iri;
}

/**
* Retrieves a role based on its IRI.
*
* <p>This method iterates over all available roles and checks if any role's IRI
* matches the provided IRI string. If a match is found, the corresponding role
* is returned as an Optional. If no match is found, an empty Optional is returned.</p>
*
* @param iri the IRI of the role to retrieve
* @return an Optional containing the corresponding Role if found,
* or an empty Optional if no matching role exists
*/
public static Optional<Role> fromIri(String iri) {
for (Role r : values()) {
if (r.getIri().equals(iri)) {
return Optional.of(r);
}
}
return Optional.empty();
}

/**
* Retrieves a role based on its role name.
*
* <p>This method iterates over all available roles and checks if any role's
* name matches the provided name string (case-insensitive). If a match is found,
* the corresponding role is returned as an Optional. If no match is found,
* an empty Optional is returned.</p>
*
* @param name the name of the role to retrieve
* @return an Optional containing the corresponding Role if found,
* or an empty Optional if no matching role exists
*/
public static Optional<Role> fromName(String name) {
for (Role r : values()) {
if (r.roleName.equalsIgnoreCase(name)) {
return Optional.of(r);
}
}
return Optional.empty();
}

/**
* Retrieves a role based on either its IRI or role name.
*
* <p>This method first attempts to find a role using the provided identification string
* as an IRI. If no role is found, it then attempts to find a role using the
* identification string as a role name. The first successful match will be returned
* as an Optional. If no match is found, an empty Optional is returned.</p>
*
* @param identification the IRI or role name of the role to retrieve
* @return an Optional containing the corresponding Role if found,
* or an empty Optional if no matching role exists
*/
public static Optional<Role> fromIriOrName(String identification) {
Optional<Role> role = fromIri(identification);
return role.isPresent() ? role : fromName(identification);
}
}
71 changes: 71 additions & 0 deletions src/main/java/cz/cvut/kbss/study/model/RoleGroup.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package cz.cvut.kbss.study.model;
import cz.cvut.kbss.jopa.model.annotations.*;
import cz.cvut.kbss.study.model.util.HasOwlKey;
import cz.cvut.kbss.study.model.util.HasUri;
import cz.cvut.kbss.study.util.Constants;

import java.io.Serializable;
import java.net.URI;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

@OWLClass(iri = Vocabulary.s_c_role_group)
public class RoleGroup implements Serializable, HasUri {

@Id
private URI uri;

@OWLAnnotationProperty(iri = Vocabulary.s_p_label)
private String name;

@Enumerated(EnumType.OBJECT_ONE_OF)
@OWLObjectProperty(iri = Vocabulary.s_p_has_role)
private Set<Role> roles = new HashSet<>();

public void addRole(Role role){
roles.add(role);
}


public URI getUri() {
return uri;
}

public void setUri(URI uri) {
this.uri = uri;
}

public String getName() {
return name;
}

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

public Set<Role> getRoles() {
return roles;
}

public void setRoles(Set<Role> roles) {
this.roles = roles;
}

public void generateUri() {
this.uri = URI.create(Constants.BASE_URI + name);
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RoleGroup roleGroup = (RoleGroup) o;
return Objects.equals(name, roleGroup.name);
}

@Override
public int hashCode() {
return Objects.hash(name);
}
}
29 changes: 9 additions & 20 deletions src/main/java/cz/cvut/kbss/study/model/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,16 @@
import cz.cvut.kbss.jopa.model.annotations.OWLDataProperty;
import cz.cvut.kbss.jopa.model.annotations.OWLObjectProperty;
import cz.cvut.kbss.jopa.model.annotations.ParticipationConstraints;
import cz.cvut.kbss.jopa.model.annotations.Types;
import cz.cvut.kbss.study.model.util.HasDerivableUri;
import cz.cvut.kbss.study.util.Constants;
import cz.cvut.kbss.study.util.IdentificationUtils;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;

@OWLClass(iri = Vocabulary.s_c_Person)
public class User implements HasDerivableUri, Serializable {
Expand Down Expand Up @@ -61,12 +57,11 @@ public class User implements HasDerivableUri, Serializable {
@OWLObjectProperty(iri = Vocabulary.s_p_is_member_of, fetch = FetchType.EAGER)
private Institution institution;

@Types
private Set<String> types;
@OWLObjectProperty(iri = Vocabulary.s_p_has_role_group, fetch = FetchType.EAGER)
private RoleGroup roleGroup;

public User() {
this.types = new HashSet<>();
types.add(Vocabulary.s_c_doctor);

}

@Override
Expand Down Expand Up @@ -137,17 +132,12 @@ public void setInstitution(Institution institution) {
this.institution = institution;
}

public Set<String> getTypes() {
return types;
}

public void setTypes(Set<String> types) {
this.types = types;
public RoleGroup getRoleGroup() {
return roleGroup;
}

public void addType(String type) {
assert types != null;
getTypes().add(type);
public void setRoleGroup(RoleGroup roleGroup) {
this.roleGroup = roleGroup;
}

/**
Expand All @@ -158,8 +148,7 @@ public void addType(String type) {
* @return {@code true} if this is admin, {@code false} otherwise
*/
public boolean isAdmin() {
assert types != null;
return getTypes().contains(Vocabulary.s_c_administrator);
return roleGroup != null && roleGroup.getRoles().contains(Role.administrator);
}

public String getToken() {
Expand Down Expand Up @@ -216,7 +205,7 @@ public User copy() {
copy.setInstitution(institution);
copy.setIsInvited(isInvited);
copy.setToken(token);
types.forEach(copy::addType);
copy.setRoleGroup(roleGroup);
return copy;
}

Expand Down
44 changes: 44 additions & 0 deletions src/main/java/cz/cvut/kbss/study/persistence/dao/RoleGroupDao.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package cz.cvut.kbss.study.persistence.dao;

import cz.cvut.kbss.jopa.exceptions.NoResultException;
import cz.cvut.kbss.jopa.model.EntityManager;
import cz.cvut.kbss.study.exception.PersistenceException;
import cz.cvut.kbss.study.model.RoleGroup;
import cz.cvut.kbss.study.util.Constants;
import java.util.Objects;
import org.springframework.stereotype.Repository;
import java.net.URI;
import cz.cvut.kbss.study.model.Vocabulary;

@Repository
public class RoleGroupDao extends BaseDao<RoleGroup> {

protected RoleGroupDao(EntityManager em) {
super(RoleGroup.class, em);
}

public RoleGroup findByName(String name) {
if (name == null) {
return null;
}
try {
return em.createNativeQuery("SELECT ?x WHERE { ?x ?hasName ?name . }", RoleGroup.class)
.setParameter("hasName", URI.create(Vocabulary.s_p_label))
.setParameter("name", name, Constants.PU_LANGUAGE).getSingleResult();
} catch (NoResultException e) {
return null;
}
}

@Override
public void persist(RoleGroup entity) {
Objects.requireNonNull(entity);
try {
em.persist(entity);
} catch (RuntimeException e) {
LOG.error("Error when persisting entity.", e);
throw new PersistenceException(e);
}
}

}
8 changes: 5 additions & 3 deletions src/main/java/cz/cvut/kbss/study/persistence/dao/UserDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,11 @@ public List<User> findByInstitution(Institution institution) {

public int getNumberOfInvestigators() {
return ((BigInteger) em.createNativeQuery(
"SELECT (count(?p) as ?investigatorCount) WHERE { ?p a ?typeDoctor . MINUS {?p a ?typeAdmin}}")
.setParameter("typeDoctor", URI.create(Vocabulary.s_c_doctor))
.setParameter("typeAdmin", URI.create(Vocabulary.s_c_administrator)).getSingleResult()
"SELECT (count(?p) as ?investigatorCount) WHERE { ?p a ?typeUser . MINUS {?p ?hasRoleGroup ?roleGroup . ?roleGroup ?hasRole ?typeAdmin}}")
blcham marked this conversation as resolved.
Show resolved Hide resolved
.setParameter("typeUser", URI.create(Vocabulary.s_c_Person))
.setParameter("hasRoleGroup", URI.create(Vocabulary.s_p_has_role_group))
.setParameter("hasRole", URI.create(Vocabulary.s_p_has_role))
.setParameter("typeAdmin", URI.create(Vocabulary.s_i_RM_ADMIN)).getSingleResult()
).intValue();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public void create(@RequestBody ActionHistory actionHistory) {
}
}

@PreAuthorize("hasRole('" + SecurityConstants.ROLE_ADMIN + "')")
@PreAuthorize("hasAuthority('" + SecurityConstants.ROLE_ADMIN + "')")
@GetMapping(produces = MediaType.APPLICATION_JSON_VALUE)
public List<ActionHistory> getActions(@RequestParam(value = "author", required = false) String authorUsername,
@RequestParam(value = "type", required = false) String type,
Expand All @@ -73,7 +73,7 @@ public List<ActionHistory> getActions(@RequestParam(value = "author", required =
return result.getContent();
}

@PreAuthorize("hasRole('" + SecurityConstants.ROLE_ADMIN + "')")
@PreAuthorize("hasAuthority('" + SecurityConstants.ROLE_ADMIN + "')")
@GetMapping(value = "/{key}", produces = MediaType.APPLICATION_JSON_VALUE)
public ActionHistory getByKey(@PathVariable("key") String key) {
final ActionHistory action = actionHistoryService.findByKey(key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import org.springframework.web.bind.annotation.*;

@RestController
@PreAuthorize("hasRole('" + SecurityConstants.ROLE_USER + "')")
@PreAuthorize("hasAuthority('" + SecurityConstants.ROLE_USER + "')")
@RequestMapping("/formGen")
public class FormGenController extends BaseController {

Expand Down
Loading
Loading