Skip to content

Commit

Permalink
Merge pull request #2028 from akto-api-security/feature/custom_roles_2
Browse files Browse the repository at this point in the history
Feature/custom roles 2
  • Loading branch information
notshivansh authored Jan 29, 2025
2 parents b68ec46 + a09f9df commit ad407e3
Show file tree
Hide file tree
Showing 33 changed files with 1,205 additions and 184 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -269,13 +269,13 @@ public String createNewAccount() {
}
}

User user = initializeAccount(email, newAccountId, newAccountName,true, RBAC.Role.ADMIN);
User user = initializeAccount(email, newAccountId, newAccountName,true, RBAC.Role.ADMIN.name());
getSession().put("user", user);
getSession().put("accountId", newAccountId);
return Action.SUCCESS.toUpperCase();
}

public static User initializeAccount(String email, int newAccountId, String newAccountName, boolean isNew, RBAC.Role role) {
public static User initializeAccount(String email, int newAccountId, String newAccountName, boolean isNew, String role) {
User user = UsersDao.addAccount(email, newAccountId, newAccountName);
RBACDao.instance.insertOne(new RBAC(user.getId(), role, newAccountId));
Context.accountId.set(newAccountId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -794,6 +794,23 @@ public String updateUserCollections() {
int userId = Integer.parseInt(entry.getKey());
Set<Integer> apiCollections = new HashSet<>(entry.getValue());

/*
* Need actual role, not base role,
* thus using direct Rbac query, not cached map.
*/
RBAC rbac = RBACDao.instance.findOne(Filters.and(
Filters.eq(RBAC.USER_ID, userId),
Filters.eq(RBAC.ACCOUNT_ID, accountId)));
String role = rbac.getRole();
CustomRole customRole = CustomRoleDao.instance.findRoleByName(role);
/*
* If the role is custom role, only update the user with the delta.
*/
if (customRole != null && customRole.getApiCollectionsId() != null
&& !customRole.getApiCollectionsId().isEmpty()) {
apiCollections.removeAll(customRole.getApiCollectionsId());
}

RBACDao.updateApiCollectionAccess(userId, accountId, apiCollections);
UsersCollectionsList.deleteCollectionIdsFromCache(userId, accountId);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public void sendMixpanelEvent() {
try {
int accountId = Context.accountId.get();
DashboardMode dashboardMode = DashboardMode.getDashboardMode();
RBAC record = RBACDao.instance.findOne(RBAC.ACCOUNT_ID, accountId, RBAC.ROLE, Role.ADMIN);
RBAC record = RBACDao.instance.findOne(RBAC.ACCOUNT_ID, accountId, RBAC.ROLE, Role.ADMIN.name());
if (record == null) {
return;
}
Expand Down
28 changes: 22 additions & 6 deletions apps/dashboard/src/main/java/com/akto/action/InviteUserAction.java
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package com.akto.action;

import com.akto.dao.CustomRoleDao;
import com.akto.dao.PendingInviteCodesDao;
import com.akto.dao.RBACDao;
import com.akto.dao.UsersDao;
import com.akto.dao.context.Context;
import com.akto.dto.CustomRole;
import com.akto.dto.PendingInviteCode;
import com.akto.dto.RBAC;
import com.akto.dto.RBAC.Role;
import com.akto.dto.User;
import com.akto.log.LoggerMaker;
import com.akto.notifications.email.SendgridEmail;
Expand Down Expand Up @@ -85,7 +87,7 @@ private static boolean isSameDomain(String inviteeDomain, String adminDomain) {
}

private String finalInviteCode;
private RBAC.Role inviteeRole;
private String inviteeRole;

private static final LoggerMaker loggerMaker = new LoggerMaker(InviteUserAction.class, LoggerMaker.LogDb.DASHBOARD);

Expand All @@ -108,9 +110,23 @@ public String execute() {
return ERROR.toUpperCase();
}

RBAC.Role userRole = RBACDao.getCurrentRoleForUser(user_id, Context.accountId.get());
Role userRole = RBACDao.getCurrentRoleForUser(user_id, Context.accountId.get());
Role baseRole = null;

if (!Arrays.asList(userRole.getRoleHierarchy()).contains(this.inviteeRole)) {
CustomRole customRole = CustomRoleDao.instance.findRoleByName(this.inviteeRole);

try {
if (customRole != null) {
baseRole = Role.valueOf(customRole.getBaseRole());
} else {
baseRole = Role.valueOf(this.inviteeRole);
}
} catch (Exception e) {
addActionError("Invalid role");
return ERROR.toUpperCase();
}

if (!Arrays.asList(userRole.getRoleHierarchy()).contains(baseRole)) {
addActionError("User not allowed to invite for this role");
return ERROR.toUpperCase();
}
Expand Down Expand Up @@ -210,11 +226,11 @@ public String getFinalInviteCode() {
return finalInviteCode;
}

public RBAC.Role getInviteeRole() {
public String getInviteeRole() {
return inviteeRole;
}

public void setInviteeRole(RBAC.Role inviteeRole) {
public void setInviteeRole(String inviteeRole) {
this.inviteeRole = inviteeRole;
}
}
210 changes: 210 additions & 0 deletions apps/dashboard/src/main/java/com/akto/action/RoleAction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
package com.akto.action;

import java.util.List;

import com.akto.dao.CustomRoleDao;
import com.akto.dao.PendingInviteCodesDao;
import com.akto.dao.RBACDao;
import com.akto.dao.context.Context;
import com.akto.dto.CustomRole;
import com.akto.dto.PendingInviteCode;
import com.akto.dto.RBAC;
import com.akto.dto.RBAC.Role;
import com.akto.util.Pair;
import com.mongodb.BasicDBObject;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.Updates;

public class RoleAction extends UserAction {

/*
* Create Role.
* Update Role.
* Delete Role. -> If no user is associated with the role.
* Get Roles.
*/

List<CustomRole> roles;

public List<CustomRole> getRoles() {
return roles;
}

public String getCustomRoles() {
/*
* Need all data for a role,
* thus no projections being used.
*/
roles = CustomRoleDao.instance.findAll(new BasicDBObject());
return SUCCESS.toUpperCase();
}

List<Integer> apiCollectionIds;

public void setApiCollectionIds(List<Integer> apiCollectionIds) {
this.apiCollectionIds = apiCollectionIds;
}

String roleName;

public void setRoleName(String roleName) {
this.roleName = roleName;
}

String baseRole;

public void setBaseRole(String baseRole) {
this.baseRole = baseRole;
}

boolean defaultInviteRole;

public void setDefaultInviteRole(boolean defaultInviteRole) {
this.defaultInviteRole = defaultInviteRole;
}

private static final int MAX_ROLE_NAME_LENGTH = 50;

public boolean validateRoleName() {
if (this.roleName == null || this.roleName.isEmpty()) {
addActionError("Role names cannot be empty.");
return false;
}

if (this.roleName.length() > MAX_ROLE_NAME_LENGTH) {
addActionError("Role names cannot be more than " + MAX_ROLE_NAME_LENGTH + " characters.");
return false;
}

for (char c : this.roleName.toCharArray()) {
boolean alphabets = (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z');
boolean numbers = c >= '0' && c <= '9';
boolean specialChars = c == '-' || c == '_';

if (!(alphabets || numbers || specialChars)) {
addActionError("Role names can only be alphanumeric and contain '-'and '_'");
return false;
}
}

try {
/*
* We do not want role name from the reserved names.
*/
Role.valueOf(this.roleName);
addActionError(this.roleName + " is a reserved keyword.");
return false;
} catch(Exception e){
}

return true;
}

private boolean defaultInviteCheck(){
if(defaultInviteRole){
List<CustomRole> roles = CustomRoleDao.instance.findAll(new BasicDBObject());
for(CustomRole role: roles){
if(role.getDefaultInviteRole()){
addActionError("Default invite role already exists.");
return false;
}
}
}
return true;
}

public String createCustomRole() {

if (!validateRoleName()) {
return ERROR.toUpperCase();
}

// Always save Upper-case.
roleName = roleName.toUpperCase();

CustomRole existingRole = CustomRoleDao.instance.findRoleByName(roleName);

if (existingRole != null) {
addActionError("Existing role with same name exists.");
return ERROR.toUpperCase();
}
try {
Role.valueOf(baseRole);
} catch (Exception e) {
addActionError("Base role does not exist");
return ERROR.toUpperCase();
}

if(!defaultInviteCheck()){
return ERROR.toUpperCase();
}

CustomRole role = new CustomRole(roleName, baseRole, apiCollectionIds, defaultInviteRole);
CustomRoleDao.instance.insertOne(role);
RBACDao.instance.deleteUserEntryFromCache(new Pair<>(getSUser().getId(), Context.accountId.get()));
return SUCCESS.toUpperCase();
}

public String updateCustomRole(){
if (!validateRoleName()) {
return ERROR.toUpperCase();
}
CustomRole existingRole = CustomRoleDao.instance.findRoleByName(roleName);

if (existingRole == null) {
addActionError("Role does not exist.");
return ERROR.toUpperCase();
}

try {
Role.valueOf(baseRole);
} catch (Exception e) {
addActionError("Base role does not exist");
return ERROR.toUpperCase();
}

if(!defaultInviteCheck() && !existingRole.getDefaultInviteRole()){
return ERROR.toUpperCase();
}

CustomRoleDao.instance.updateOne(Filters.eq(CustomRole._NAME, roleName),Updates.combine(
Updates.set(CustomRole.BASE_ROLE, baseRole),
Updates.set(CustomRole.API_COLLECTIONS_ID, apiCollectionIds),
Updates.set(CustomRole.DEFAULT_INVITE_ROLE, defaultInviteRole)
));
RBACDao.instance.deleteUserEntryFromCache(new Pair<>(getSUser().getId(), Context.accountId.get()));

return SUCCESS.toUpperCase();
}

public String deleteCustomRole(){
CustomRole existingRole = CustomRoleDao.instance.findRoleByName(roleName);

if (existingRole == null) {
addActionError("Role does not exist.");
return ERROR.toUpperCase();
}

List<RBAC> usersWithRole = RBACDao.instance.findAll(Filters.eq(RBAC.ROLE, roleName));

if(!usersWithRole.isEmpty()){
addActionError("Role is associated with users. Cannot delete.");
return ERROR.toUpperCase();
}

/*
* Alt. approach: Delete all pending invites associated with the role.
*/
List<PendingInviteCode> pendingInviteCodes = PendingInviteCodesDao.instance.findAll(Filters.eq(PendingInviteCode.INVITEE_ROLE, roleName));
if(!pendingInviteCodes.isEmpty()){
addActionError("Role is associated with pending invites. Cannot delete.");
return ERROR.toUpperCase();
}

CustomRoleDao.instance.deleteAll(Filters.eq(CustomRole._NAME, roleName));
RBACDao.instance.deleteUserEntryFromCache(new Pair<>(getSUser().getId(), Context.accountId.get()));

return SUCCESS.toUpperCase();
}

}
Loading

0 comments on commit ad407e3

Please sign in to comment.