Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
0179f7a
[NAE-1617] Refactor Authority
renczesstefan Apr 29, 2022
90d3341
[NAE-1617] Refactor Authority
renczesstefan Apr 29, 2022
a50fd02
[NAE-1617] Refactor Authority
renczesstefan Apr 29, 2022
a14d450
[NAE-1617] Refactor Authority
renczesstefan Apr 29, 2022
19befc8
[NAE-1617] Refactor Authority
renczesstefan Apr 29, 2022
3e6f56e
[NAE-1617] Refactor Authority
renczesstefan Apr 29, 2022
5d6ee53
[NAE-1617] Refactor Authority
renczesstefan Apr 29, 2022
6f17119
[NAE-1617] Refactor Authority
renczesstefan Apr 29, 2022
552cb90
[NAE-1617] Refactor Authority
renczesstefan May 2, 2022
d0c02a2
[NAE-1617] Refactor Authority
renczesstefan May 2, 2022
4c1e918
[NAE-1617] Refactor Authority
renczesstefan May 2, 2022
6e45491
[NAE-1617] Refactor Authority
renczesstefan May 2, 2022
70574f9
[NAE-1617] Refactor Authority
renczesstefan May 2, 2022
f022afc
[NAE-1617] Refactor Authority
renczesstefan May 3, 2022
bd79076
[NAE-1617] Refactor Authority
renczesstefan May 4, 2022
043e478
[NAE-1617] Refactor Authority
renczesstefan May 4, 2022
1de66c7
[NAE-1617] Refactor Authority
renczesstefan May 4, 2022
361e660
[NAE-1617] Refactor Authority
renczesstefan May 4, 2022
1c090f2
[NAE-1617] Refactor Authority
renczesstefan May 4, 2022
d93846c
[NAE-1617] Refactor Authority
renczesstefan May 4, 2022
bf5ae40
[NAE-1617] Refactor Authority
renczesstefan May 5, 2022
d07bb75
[NAE-1617] Refactor Authority
renczesstefan May 5, 2022
81185d3
[NAE-1617] Refactor Authority
renczesstefan May 6, 2022
e8e0efa
Merge branch 'release/6.1.0' into NAE-1617
renczesstefan May 9, 2022
3e20ed2
[NAE-1617] Refactor Authority
renczesstefan May 9, 2022
f45da11
[NAE-1617] Refactor Authority
renczesstefan May 16, 2022
af0da4c
[NAE-1617] Refactor Authority
renczesstefan May 16, 2022
8e34591
[NAE-1617] Refactor Authority
renczesstefan May 16, 2022
cb19c59
[NAE-1617] Refactor Authority
renczesstefan May 16, 2022
f6b7fe5
[NAE-1617] Refactor Authority
renczesstefan May 16, 2022
012922a
[NAE-1617] Refactor Authority
renczesstefan May 16, 2022
47044e3
[NAE-1617] Refactor Authority
renczesstefan May 16, 2022
adf1710
[NAE-1617] Refactor Authority
renczesstefan May 16, 2022
6dfca41
[NAE-1617] Refactor Authority
renczesstefan May 16, 2022
c554bfa
[NAE-1617] Refactor Authority
renczesstefan May 16, 2022
534dbdf
[NAE-1617] Refactor authority
renczesstefan May 18, 2022
8c6fa3f
[NAE-1617] Refactor authority
renczesstefan May 18, 2022
0240935
[NAE-1617] Refactor authority
renczesstefan May 18, 2022
dc062f7
[NAE-1617] Refactor authority
renczesstefan May 18, 2022
aa61971
[NAE-1617] Authority refactor
renczesstefan May 23, 2022
601ef6b
[NAE-1617] Authority refactor
renczesstefan May 23, 2022
ce6b750
[NAE-1617] Authority refactor
renczesstefan May 23, 2022
5575b9b
[NAE-1567] Plugin management
renczesstefan May 27, 2022
c9fdc43
[NAE-1617] Authority refactor
renczesstefan May 30, 2022
03b995a
Merge branch 'release/6.2.0' into NAE-1617
renczesstefan Aug 2, 2022
f1a3d68
[NAE-1617] Refactor authority
renczesstefan Aug 2, 2022
378e109
[NAE-1617] Refactor authority
renczesstefan Aug 2, 2022
2203dee
[NAE-1617] Refactor authority
renczesstefan Aug 2, 2022
71f5e96
[NAE-1617] Refactor authority
renczesstefan Aug 2, 2022
f36bd15
[NAE-1617] Refactor authority
renczesstefan Aug 2, 2022
2bec5eb
[NAE-1617] Refactor authority
renczesstefan Aug 2, 2022
017db43
[NAE-1617] Refactor authority
renczesstefan Aug 2, 2022
a60bb47
[NAE-1617] Authority refactor
renczesstefan Sep 12, 2022
5a92bf6
Merge branch 'release/6.2.0' into NAE-1617
renczesstefan Sep 12, 2022
07befdd
Merge branch 'release/6.2.0' into NAE-1617
renczesstefan Sep 12, 2022
cac5fb3
Merge branch 'release/6.2.0' into NAE-1617
renczesstefan Sep 13, 2022
7c91908
[NAE-1617] Authority refactor
renczesstefan Sep 13, 2022
7f2a38b
[NAE-1617] Authority refactor
renczesstefan Sep 13, 2022
b573dfa
[NAE-1617] Authority refactor
renczesstefan Sep 13, 2022
8b6bd3f
[NAE-1617] Authority refactor
renczesstefan Sep 13, 2022
f89384c
[NAE-1617] Authority refactor
renczesstefan Sep 13, 2022
6419576
[NAE-1617] Authority refactor
renczesstefan Sep 13, 2022
fc53497
[NAE-1617] Authority refactor
renczesstefan Sep 13, 2022
7d77928
[NAE-1617] Authority refactor
renczesstefan Sep 13, 2022
6ad9216
[NAE-1617] Authority refactor
renczesstefan Sep 13, 2022
0420715
[NAE-1617] Authority refactor
renczesstefan Sep 13, 2022
62a8600
[NAE-1617] Authority refactor
renczesstefan Sep 13, 2022
74e35cf
[NAE-1617] Authority refactor
renczesstefan Sep 13, 2022
5f26dc4
[NAE-1617] Authority refactor
renczesstefan Sep 14, 2022
01a5b1a
Merge branch 'release/6.3.0' into NAE-1617
renczesstefan Sep 14, 2022
f41976f
[NAE-1617] Authority refactor
renczesstefan Sep 14, 2022
9209a5e
[NAE-1617] Authority refactor
renczesstefan Sep 14, 2022
1d9c3bf
[NAE-1617] Refactor authority
renczesstefan Jan 30, 2023
c945539
[NAE-1617] Refactor authority
renczesstefan Jan 31, 2023
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
120 changes: 120 additions & 0 deletions docs/authorization/authority.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Authority management
Netgrif Application Engine implements authority objects to manage access
to resources, to protect resources from unauthorized access. Then these
authorities can be assigned to users to provide access to those secured
resources.

## Authorizing objects
Authorizing objects are Java enum values, that represent authorities,
that are needed to access a resource or use an action. E.g. to be able to import new
process (Petri Net) into the application, the user must have authority
created from `AuthorizingObject.PROCESS_UPLOAD` authorizing object.

These authorizing objects are predefined, and they are used to create the
`Authority` objects/entities at application startup. There followings are predefined:

- `PROCESS_UPLOAD`- to import new process
- `PROCESS_VIEW_ALL`- to retrieve all the processes imported by any user
- `PROCESS_VIEW_OWN`- to retrieve only processes imported by logged user
- `PROCESS_DELETE_ALL`- to delete processes imported by any user
- `PROCESS_DELETE_OWN`- to delete processes imported by logged user
- `FILTER_UPLOAD`- to upload filter
- `FILTER_DELETE_OWN`- to delete filter imported by logged user
- `FILTER_DELETE_ALL`- to delete filter imported by any user
- `USER_CREATE`- to invite or create user
- `USER_DELETE`- to remove user
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in the demo applications, users are allowed to delete their own accounts. It might be helpful to split this authority into _MY and _ALL

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have split the authority to the required two authorities. In the example app and in the base engine there is no endpoint for deleting users, these authorities are only used over actions in action delegate.

Do we want to enable to delete own accounts on actions? Is there a chance that it will cause a problem, when you are calling delete action for own account over assigned task (the task can be assigned to own user)?

- `USER_EDIT_ALL`- to edit any user
- `USER_EDIT_SELF`- to edit only logged user
- `USER_VIEW_ALL`- to retrieve all users
- `USER_VIEW_SELF`- to retrieve only logged user
- `GROUP_CREATE`- to create group
- `GROUP_DELETE_OWN`- to delete group created by logged user
- `GROUP_DELETE_ALL`- to delete group create by any user
- `GROUP_ADD_USER`- to add any user to any group
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about adding users to "my" groups? Do we define the concept of "my" groups?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In demo application the user only can add users to its own group and we have defined concept of "my" groups in case GROUP_VIEW_MY so there is an endpoint to retrieve the groups owned by user. Hence I can define GROUP_ADD_USER_MY for future use.

- `GROUP_REMOVE_USER`- to remove any user from any group
Comment thread
minop marked this conversation as resolved.
- `GROUP_VIEW_ALL`- to retrieve any group
- `GROUP_VIEW_OWN`- to retrieve group of logged user
- `ROLE_ASSIGN_TO_USER`- to assign role to user
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is role assignment a general authority, or can it be restricted only to a group? Such restrictions might be useful for the demo applications.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a string constant, using this an authority is created at startup. Point 1.: I do not see the way that this string could be used to create authority for managing access to groups. Point 2.: there is no endpoint for assigning roles based on groups, but you can freely implement a method in authorization services to check for current user's group. Point 3.: as far as I know, the process roles and petri nets do not have any info about their group inside which they where created / uploaded.

These are only my thoughts, if I am wrong, please correct me, but I would consider a group refactor to update the group system.

- `ROLE_REMOVE_FROM_USER`- to remove role from user
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as ASSIGN.

Is it meaningful to separate role assignment, from role removal? What would be the use case?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, I am removing this authority.

- `AUTHORITY_CREATE`- to create authority
- `AUTHORITY_DELETE`- to delete authority
- `AUTHORITY_VIEW_ALL`- to retrieve any authority
- `CASE_VIEW_ALL`- to view all cases
- `CASE_CREATE`- to create case
- `CASE_DELETE`- to delete case
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be meaningful to separate _MY and _ALL?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only super user has the CASE_DELETE authority by default, the other user's must have some permission (user ref of role) to be able to remove a case. This permission is checked using WorkflowAuthorizationService.

If you wish, I can implement an authorization for checking authority like CASE_DELETE_OWN AND author of case is the logged user.

- `CASE_DATA_GET_ALL`- to get all data of case
- `TASK_RELOAD`- to reload tasks
- `TASK_ASSIGN`- to assign task
- `TASK_FINISH`- to finish task
- `TASK_CANCEL`- to cancel task
- `TASK_DELEGATE`- to delegate task
- `TASK_SAVE_DATA`- to save data on task
- `ELASTIC_REINDEX` - to reindex Elasticsearch database
- `LDAP_GROUP_GET_ALL` - to get all LDAP groups
- `LDAP_GROUP_ASSIGN_ROLES` - to assign roles to LDAP groups

However, it is possible to add custom authorizing objects using `nae.authority.authorizing-objects`
property in `application.properties` files as following:

```
# Authorities
nae.authority.authorizing-objects=EXAMPLE_AUTHORITY_1,EXAMPLE_AUTHORITY_2
```

## Authorization definition

You can define authorization for any method in the Netgrif Application Engine project.
You can use the new `@Authorize` annotation over the method definitions. This annotation can
be defined once or multiple times over a method:

```
@Authorize(authority = "PROCESS_UPLOAD", expression = "#canUpload(#userId)"
void importPetriNet(File petriNet) {
...
}
```

The ``@Authorize`` annotation has two fields:
- `authority` - an authority, that the authenticated user is needed to be checked for
- `expression` - an expression that returns boolean and serves as additional authorization

## Authorization check

The statements are evaluated in `BaseAuthorizationServiceAspect` bean that is implemented using AOP. The user will have
valid authorization if it has the required `authority` AND fulfills additional authorization defined
in `expression` (authority check returns `true` AND expression check returns `true`).


If there is no `authority` defined, the authorization method returns `true` for authority check.
In the following example, user must fulfill addition authorization implemented in `canUpload(String userId)`
function:
```
@Authorize(expression = "#canUpload(#userId))
void importPetriNet(File petriNet) {
...
}
```

If there is no `expression` defined, the authorization method always returns`true` for expression check.
In the following example, user must have `PROCESS_UPLOAD` authority:
```
@Authorize(authority = "PROCESS_UPLOAD")
void importPetriNet(File petriNet) {
...
}
```


If there are more authorizations defined using `@Authorize`, then OR statement is valid
between the multiple `@Authorize` statements, so user must fulfill at least one `@Authorize` statement.
In the following example, the logged user must have `PROCESS_UPLOAD` authority AND must fulfill `canUpload(String userId)`
expression, OR must fulfill `isAdmin(String userId)` expression:
```
@Authorize(authority = "PROCESS_UPLOAD", expression = "#canUpload(#userId)",
@Authorize(expression = "isAdmin(#userId)")
void importPetriNet(File petriNet) {
...
}
```


7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,13 @@
<version>2.2.0-rc2</version>
</dependency>

<!-- Language injection -->
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>23.0.0</version>
</dependency>

<!-- Dev tools -->
<dependency>
<groupId>org.springframework.boot</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package com.netgrif.application.engine.petrinet.domain.dataset.logic.action

import com.netgrif.application.engine.AsyncRunner
import com.netgrif.application.engine.auth.domain.Author
import com.netgrif.application.engine.auth.domain.Authorizations
import com.netgrif.application.engine.auth.domain.AuthorizingObject
import com.netgrif.application.engine.auth.domain.Authorize
import com.netgrif.application.engine.auth.domain.IUser
import com.netgrif.application.engine.auth.domain.LoggedUser
import com.netgrif.application.engine.auth.service.UserDetailsServiceImpl
Expand Down Expand Up @@ -1137,6 +1140,8 @@ class ActionDelegate {
mailService.sendMail(mailDraft)
}

@Authorize(authority = "USER_EDIT_ALL")
@Authorize(authority = "USER_EDIT_OWN", expression = "@userService.getLoggedUser().email.equals(#email)")
def changeUserByEmail(String email) {
[email : { cl ->
changeUserByEmail(email, "email", cl)
Expand All @@ -1153,6 +1158,8 @@ class ActionDelegate {
]
}

@Authorize(authority = "USER_EDIT_ALL")
@Authorize(authority = "USER_EDIT_OWN", expression = "@userService.getLoggedUser().stringId.equals(#id)")
def changeUser(String id) {
[email : { cl ->
changeUser(id, "email", cl)
Expand All @@ -1169,6 +1176,8 @@ class ActionDelegate {
]
}

@Authorize(authority = "USER_EDIT_ALL")
@Authorize(authority = "USER_EDIT_OWN", expression = "@userService.getLoggedUser().stringId.equals(#user.getStringId())")
def changeUser(IUser user) {
[email : { cl ->
changeUser(user, "email", cl)
Expand All @@ -1185,16 +1194,22 @@ class ActionDelegate {
]
}

@Authorize(authority = "USER_EDIT_ALL")
@Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().email.equals(#email)")
def changeUserByEmail(String email, String attribute, def cl) {
IUser user = userService.findByEmail(email, false)
changeUser(user, attribute, cl)
}

@Authorize(authority = "USER_EDIT_ALL")
@Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().stringId.equals(#id)")
def changeUser(String id, String attribute, def cl) {
IUser user = userService.findById(id, false)
changeUser(user, attribute, cl)
}

@Authorize(authority = "USER_EDIT_ALL")
@Authorize(authority = "USER_EDIT_SELF", expression = "@userService.getLoggedUser().stringId.equals(#user.getStringId())")
def changeUser(IUser user, String attribute, def cl) {
if (user == null) {
log.error("Cannot find user.")
Expand All @@ -1210,6 +1225,7 @@ class ActionDelegate {
userService.save(user)
}

@Authorize(authority = "USER_CREATE")
MessageResource inviteUser(String email) {
NewUserRequest newUserRequest = new NewUserRequest()
newUserRequest.email = email
Expand All @@ -1218,6 +1234,7 @@ class ActionDelegate {
return inviteUser(newUserRequest)
}

@Authorize(authority = "USER_CREATE")
MessageResource inviteUser(NewUserRequest newUserRequest) {
IUser user = registrationService.createNewUser(newUserRequest);
if (user == null)
Expand All @@ -1228,13 +1245,15 @@ class ActionDelegate {
return MessageResource.successMessage("Done");
}

@Authorize(authority = "USER_DELETE")
void deleteUser(String email) {
IUser user = userService.findByEmail(email, false)
if (user == null)
log.error("Cannot find user with email [" + email + "]")
deleteUser(user)
}

@Authorize(authority = "USER_DELETE")
void deleteUser(IUser user) {
List<Task> tasks = taskService.findByUser(new FullPageRequest(), user).toList()
if (tasks != null && tasks.size() > 0)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,30 @@
package com.netgrif.application.engine.startup

import com.netgrif.application.engine.auth.domain.Authority
import com.netgrif.application.engine.auth.domain.AuthorizingObject
import com.netgrif.application.engine.auth.service.interfaces.IAuthorityService
import groovy.util.logging.Slf4j
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component

@Slf4j
@Component
class AuthorityRunner extends AbstractOrderedCommandLineRunner {

@Value('${nae.authority.authorizing-objects:}')
private String[] additionalAuthorizingObjects

@Autowired
private IAuthorityService service

@Override
void run(String... strings) throws Exception {
service.getOrCreate(Authority.user)
service.getOrCreate(Authority.admin)
service.getOrCreate(Authority.systemAdmin)
service.getOrCreate(Authority.anonymous)
createAll()
}

void createAll() {
AuthorizingObject.values().toList().forEach(authority -> service.getOrCreate(authority.name()))
additionalAuthorizingObjects.toList().forEach(authority -> service.getOrCreate(authority))
log.info("Authorities created.")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,12 @@ class ImportHelper {


@SuppressWarnings("GroovyAssignabilityCheck")
Map<String, Authority> createAuthorities(Map<String, String> authorities) {
HashMap<String, Authority> authoritities = new HashMap<>()
Map<String, List<Authority>> createAuthorities(Map<String, List<String>> authorities) {
HashMap<String, List<Authority>> authoritities = new HashMap<>()
authorities.each { authority ->
authoritities.put(authority.key, authorityService.getOrCreate(authority.value))
if (!authoritities.containsKey(authority.key))
authoritities.put(authority.key, new ArrayList<Authority>())
authoritities.get(authority.key).addAll(authorityService.getOrCreate(authority.value))
}

log.info("Creating ${authoritities.size()} authorities")
Expand Down Expand Up @@ -123,24 +125,6 @@ class ImportHelper {
return Optional.of(petriNet)
}

// ProcessRole createUserProcessRole(PetriNet net, String name) {
// ProcessRole role = processRoleRepository.save(new ProcessRole(roleId:
// net.roles.values().find { it -> it.name.defaultValue == name }.stringId, netId: net.getStringId()))
// log.info("Created user process role $name")
// return role
// }
//
// Map<String, ProcessRole> createUserProcessRoles(Map<String, String> roles, PetriNet net) {
// HashMap<String, ProcessRole> userRoles = new HashMap<>()
// roles.each { it ->
// userRoles.put(it.key, createUserProcessRole(net, it.value))
// }
//
// log.info("Created ${userRoles.size()} process roles")
// return userRoles
// }


ProcessRole getProcessRoleByImportId(PetriNet net, String roleId) {
ProcessRole role = net.roles.values().find { it -> it.importId == roleId }
return role
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ class SuperCreator extends AbstractOrderedCommandLineRunner {
}

private IUser createSuperUser() {
Authority adminAuthority = authorityService.getOrCreate(Authority.admin)
Authority systemAuthority = authorityService.getOrCreate(Authority.systemAdmin)

Set<Authority> adminAuthorities = authorityService.getDefaultAdminAuthorities()
IUser superUser = userService.findByEmail("super@netgrif.com", false)
if (superUser == null) {
this.superUser = userService.saveNew(new User(
Expand All @@ -54,14 +52,19 @@ class SuperCreator extends AbstractOrderedCommandLineRunner {
email: "super@netgrif.com",
password: superAdminPassword,
state: UserState.ACTIVE,
authorities: [adminAuthority, systemAuthority] as Set<Authority>,
authorities: adminAuthorities as Set<Authority>,
processRoles: processRoleService.findAll() as Set<ProcessRole>))
log.info("Super user created")
} else {
log.info("Super user detected")
this.superUser = superUser
}

adminAuthorities.forEach({
it.addUser(this.superUser)
authorityService.save(it)
})

return this.superUser
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,17 @@ public AbstractUser() {
}

public void addAuthority(Authority authority) {
if (authorities.stream().anyMatch(it -> it.get_id().equals(authority.get_id())))
if (authorities.stream().anyMatch(it -> it.getName().equals(authority.getName())))
return;
authorities.add(authority);
}

public void removeAuthority(Authority authority) {
if (authorities.stream().noneMatch(it -> it.getName().equals(authority.getName())))
return;
authorities.remove(authority);
}

public void addProcessRole(ProcessRole role) {
if (processRoles.stream().anyMatch(it -> it.getStringId().equals(role.getStringId())))
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@

import lombok.Data;
import org.bson.types.ObjectId;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.mongodb.core.mapping.Document;

import java.util.Set;

@Document
@Data
public class AnonymousUser extends User {
Expand Down
Loading