Skip to content

Commit

Permalink
Extend repository service and authority parsing for GraphQL role support
Browse files Browse the repository at this point in the history
- Repositories Service:
  * Updated `getReadableRepositories()` to accept an optional `graphql` parameter
    and delegate to `$jwtAuth.canReadRepo(repo, graphql)`.

- JWT Auth Service:
  * Changed `canReadRepo()` and `canWriteRepo()` to accept `graphql = false`
    and adjust logic within `checkRights()` to skip or include entries ending with `:GRAPHQL`.
  * Ensured that Admin users or wildcard roles properly override new GraphQL checks.

- Authorities Util:
  * Consolidated logic for `READ`, `WRITE`, and `GRAPHQL` prefixes in a single block.
  * Updated the `getRepoFromAuthority()` function to handle `GRAPHQL_PREFIX`.
  * Adjusted parse logic to set `.read`, `.write`, or `.graphql` on a per-repo basis.

- User Mappers & Map-Object:
  * Enhanced `mapObject()` to allow optional `newKey` renaming and `removeOldKey`.
  * Renamed the user’s `grantedAuthorities` field to `grantedAuthoritiesUiModel` when mapping back from BE data.

- Cypress:
  * Added methods for toggling read, write, and GraphQL checkboxes on a per-repo or wildcard basis.
  * Enhanced test coverage in `user-and-access.spec.js` to handle combinations of read/write/GraphQL.
  * Introduced `editUserAuths()` for editing existing permissions, and improved `assertUserAuths()` negative checks.
  • Loading branch information
teodossidossev committed Feb 18, 2025
1 parent 59bc744 commit 87c30d1
Show file tree
Hide file tree
Showing 18 changed files with 803 additions and 251 deletions.
43 changes: 43 additions & 0 deletions src/css/user.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
.wb-user .auth-column-label {
margin-right: 4px;
}

.wb-user .repository-column {
width: 50%;
}

.wb-user .read-column {
width: 15%;
}

.wb-user .write-column {
width: 15%;
}

.wb-user .write-column,
.wb-user .write-rights,
.wb-user .write-any {
border-right: 1px solid #eceeef;
}

.wb-user .graphql-column {
width: 15%;
}
.wb-user .table-fixed {
table-layout: fixed;
width: 100%;
}

.wb-user .bordered-table {
border: 1px solid #eceeef;
border-collapse: collapse;
width: 100%;
}

.wb-user .graphql-icon {
margin-left: 4px;
}

.wb-user .repository-name {
word-wrap: break-word;
}
10 changes: 7 additions & 3 deletions src/i18n/locale-en.json
Original file line number Diff line number Diff line change
Expand Up @@ -1269,9 +1269,12 @@
"security.user.role.too.short": "Must be at least 2 symbols long",
"security.no.active.location": "There is no active location.",
"security.repository.title": "Repository",
"security.tooltip.read": "Read",
"security.tooltip.write": "Write",
"security.tooltip.graphql": "GraphQL only",
"security.tooltip.read": "Read rights",
"security.tooltip.write": "Write rights",
"security.tooltip.graphql": "GraphQL only rights",
"security.label.read": "Read",
"security.label.write": "Write",
"security.label.graphql": "GraphQL only",
"security.user.may": "User may:",
"security.use.gdb": "Use GraphDB",
"security.grant.read.access": "Be granted read access to a repository",
Expand Down Expand Up @@ -2121,6 +2124,7 @@
"repo.choose.location.warning": "select a location first",
"repo.properties": "Repository properties",
"repo.id.label": "Repository ID*",
"repo.local": "Local",
"edit.repo.id.tooltip": "Edit repository id",
"edit.repo.id.cannot_edit_in_cluster.tooltip": "Cannot rename repository while in cluster.",
"invalid.repo.name.error": "Repository name can contain only letters (a-z, A-Z), numbers (0-9), \"-\" and \"_\"",
Expand Down
10 changes: 7 additions & 3 deletions src/i18n/locale-fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -1269,9 +1269,12 @@
"security.user.role.too.short": "Doit contenir au moins 2 symboles",
"security.no.active.location": "Il n'y a pas de dépôt actif.",
"security.repository.title": "Dépôt",
"security.tooltip.read": "Lire",
"security.tooltip.write": "Ecriture",
"security.tooltip.graphql": "GraphQL uniquement",
"security.tooltip.read": "Droits de lecture",
"security.tooltip.write": "Droits d'écriture",
"security.tooltip.graphql": "Droits GraphQL uniquement",
"security.label.read": "Lire",
"security.label.write": "Ecriture",
"security.label.graphql": "GraphQL uniquement",
"security.user.may": "L'utilisateur peut :",
"security.use.gdb": "Utiliser GraphDB",
"security.grant.read.access": "Avoir un accès en lecture à un dépôt",
Expand Down Expand Up @@ -2121,6 +2124,7 @@
"repo.choose.location.warning": "Veuillez d'abord sélectionner un lieu",
"repo.properties": "Propriétés du dépôt",
"repo.id.label": "ID du dépôt*",
"repo.local": "Local",
"edit.repo.id.tooltip": "Modifier l'ID du dépôt",
"edit.repo.id.cannot_edit_in_cluster.tooltip": "Impossible de renommer le référentiel en mode cluster.",
"invalid.repo.name.error": "Le nom du dépôt ne peut contenir que des lettres (a-z, A-Z), des chiffres (0-9), \"-\" et \"_\".",
Expand Down
9 changes: 5 additions & 4 deletions src/js/angular/controllers.js
Original file line number Diff line number Diff line change
Expand Up @@ -437,12 +437,13 @@ function mainCtrl($scope, $menuItems, $jwtAuth, $http, toastr, $location, $repos
return $repositories.getRepositories();
};

$scope.getReadableRepositories = function () {
return $repositories.getReadableRepositories();
$scope.getReadableRepositories = function (qraphql = false) {
return $repositories.getReadableRepositories(qraphql);
};

$scope.getWritableRepositories = function () {
return $repositories.getWritableRepositories();

$scope.getWritableRepositories = function (qraphql = false) {
return $repositories.getWritableRepositories(qraphql);
};

$scope.getActiveRepository = function () {
Expand Down
52 changes: 37 additions & 15 deletions src/js/angular/core/services/jwt-auth.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ angular.module('graphdb.framework.core.services.jwtauth', [

} else {
return SecurityService.getAdminUser().then(function (res) {
that.principal = {username: 'admin', appSettings: res.appSettings, authorities: res.grantedAuthorities};
that.principal = {username: 'admin', appSettings: res.data.appSettings, authorities: res.data.grantedAuthorities};
$rootScope.$broadcast('securityInit', that.securityEnabled, true, that.hasOverrideAuth);
});
}
Expand Down Expand Up @@ -397,7 +397,7 @@ angular.module('graphdb.framework.core.services.jwtauth', [
return this.isAdmin() || this.isRepoManager();
};

this.canWriteRepo = function (repo) {
this.canWriteRepo = function (repo, graphql = false) {
if (!repo) {
return false;
}
Expand All @@ -409,13 +409,13 @@ angular.module('graphdb.framework.core.services.jwtauth', [
} else if (this.hasAdminRole()) {
return true;
}
return this.checkRights(repo, 'WRITE');
return this.checkRights(repo, 'WRITE', graphql);
} else {
return true;
}
};

this.canReadRepo = function (repo) {
this.canReadRepo = function (repo, graphql = false) {
if (!repo) {
return false;
}
Expand All @@ -427,24 +427,46 @@ angular.module('graphdb.framework.core.services.jwtauth', [
} else if (this.hasAdminRole()) {
return true;
}
return this.checkRights(repo, 'READ');
return this.checkRights(repo, 'READ', graphql);
} else {
return true;
}
};

this.checkRights = function (repo, action) {
if (repo) {
for (let i = 0; i < this.principal.authorities.length; i++) {
const authRole = this.principal.authorities[i];
const parts = authRole.split('_', 2);
const repoPart = authRole.slice(parts[0].length + parts[1].length + 2);
const repoId = repo.location ? `${repo.id}@${repo.location}` : repo.id;
if (parts[0] === action && (repoId === repoPart || repo.id !== 'SYSTEM' && repoPart === '*')) {
return true;
}

this.checkRights = function (repo, action, graphql = false) {
if (!repo) {
return false;
}

for (let i = 0; i < this.principal.authorities.length; i++) {
let authRole = this.principal.authorities[i];

// If we're checking for a non-GraphQL role, skip any that *do* end with :GRAPHQL.
const endsWithGraphQL = authRole.endsWith(':GRAPHQL');
if (!graphql && endsWithGraphQL) {
continue;
}

// Strip off :GRAPHQL if present, so the rest of the parsing remains the same.
if (endsWithGraphQL) {
authRole = authRole.slice(0, -':GRAPHQL'.length);
}

// Parse out the action (e.g. READ or WRITE), ignoring the second element 'REPO'
const parts = authRole.split('_', 2); // e.g. ["READ", "REPO"]

// Everything after "READ_REPO_" or "WRITE_REPO_" is the repository part
const repoPart = authRole.slice(parts[0].length + parts[1].length + 2);

// Construct the full repo ID with location (if any) for comparison
const repoId = repo.location ? `${repo.id}@${repo.location}` : repo.id;

if (parts[0] === action && (repoId === repoPart || repo.id !== 'SYSTEM' && repoPart === '*')) {
return true;
}
}

return false;
};

Expand Down
4 changes: 2 additions & 2 deletions src/js/angular/core/services/repositories.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,9 @@ repositories.service('$repositories', ['toastr', '$rootScope', '$timeout', '$loc
return this.getRepositories().find((repository) => repository.id === repositoryId);
};

this.getReadableRepositories = function () {
this.getReadableRepositories = function (graphql = false) {
return _.filter(this.getRepositories(), function (repo) {
return $jwtAuth.canReadRepo(repo);
return $jwtAuth.canReadRepo(repo, graphql);
});
};

Expand Down
11 changes: 11 additions & 0 deletions src/js/angular/core/services/security.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,16 @@ SecurityService.$inject = ['SecurityRestService'];
* @returns {Object} An object exposing security related operations.
*/
function SecurityService(SecurityRestService) {
const login = (username, password) => {
return SecurityRestService.login(username, password)
.then(({data, status, headers}) => {
return {
data :toUserModelMapper(data, 'authorities'),
status,
headers
}
});
}
/**
* Retrieves a user by username from the backend.
* The full response is mapped to convert its data property to a UI model.
Expand Down Expand Up @@ -167,6 +177,7 @@ function SecurityService(SecurityRestService) {
};

return {
login,
getUser,
getAuthenticatedUser,
getAdminUser,
Expand Down
13 changes: 13 additions & 0 deletions src/js/angular/rest/security.rest.service.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ angular

SecurityRestService.$inject = ['$http'];

const LOGIN_ENDPOINT = 'rest/login';
const SECURITY_ENDPOINT = 'rest/security';
const SECURITY_USER_ENDPOINT = `${SECURITY_ENDPOINT}/users`;
const SECURITY_AUTHENTICATED_ENDPOINT = `${SECURITY_ENDPOINT}/authenticated-user`;
Expand All @@ -12,6 +13,7 @@ const ROLES_ENDPOINT = 'rest/roles';

function SecurityRestService($http) {
return {
login,
getUser,
getAdminUser,
getUsers,
Expand All @@ -28,6 +30,17 @@ function SecurityRestService($http) {
getAuthenticatedUser
};

function login(username, password) {
return $http({
method: 'POST',
url: LOGIN_ENDPOINT,
data: {
username,
password
}
});
}

function getUser(username) {
return $http.get(`${SECURITY_USER_ENDPOINT}/${fixedEncodeURIComponent(username)}`);
}
Expand Down
Loading

0 comments on commit 87c30d1

Please sign in to comment.