Skip to content

Commit

Permalink
Move permission/admin checks to backend (#2824)
Browse files Browse the repository at this point in the history
* Move permission/admin checks to backend

* Add unauthorized tests
  • Loading branch information
imnasnainaec authored Dec 7, 2023
1 parent 4e0e257 commit bdef32d
Show file tree
Hide file tree
Showing 12 changed files with 283 additions and 82 deletions.
9 changes: 9 additions & 0 deletions Backend.Tests/Controllers/UserControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -217,5 +217,14 @@ public void TestIsEmailUnavailable()
var result5 = (ObjectResult)_userController.IsEmailUnavailable("").Result;
Assert.That(result5.Value, Is.True);
}

[Test]
public void TestIsUserSiteAdminNotAuthorized()
{
_userController.ControllerContext.HttpContext = PermissionServiceMock.UnauthorizedHttpContext();
var result = _userController.IsUserSiteAdmin().Result;
Assert.That(result, Is.InstanceOf<ObjectResult>());
Assert.That(((ObjectResult)result).Value, Is.False);
}
}
}
9 changes: 9 additions & 0 deletions Backend.Tests/Controllers/UserRoleControllerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ public async Task TestGetAllUserRolesNotAuthorized()
Assert.That(result, Is.InstanceOf<ForbidResult>());
}

[Test]
public async Task TestHasPermissionNotAuthorized()
{
_userRoleController.ControllerContext.HttpContext = PermissionServiceMock.UnauthorizedHttpContext();
var result = await _userRoleController.HasPermission(_projId, Permission.WordEntry);
Assert.That(result, Is.InstanceOf<ObjectResult>());
Assert.That(((ObjectResult)result).Value, Is.False);
}

[Test]
public async Task TestGetCurrentPermissions()
{
Expand Down
8 changes: 8 additions & 0 deletions Backend/Controllers/UserController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,14 @@ public async Task<IActionResult> DeleteUser(string userId)
return NotFound(userId);
}

/// <summary> Checks if current user is a site administrator. </summary>
[HttpGet("issiteadmin", Name = "IsUserSiteAdmin")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(bool))]
public async Task<IActionResult> IsUserSiteAdmin()
{
return Ok(await _permissionService.IsSiteAdmin(HttpContext));
}

/// <remarks>
/// This is used in a [FromBody] serializer, so its attributes cannot be set to readonly.
/// </remarks>
Expand Down
8 changes: 8 additions & 0 deletions Backend/Controllers/UserRoleController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,14 @@ public async Task<IActionResult> DeleteProjectUserRoles(string projectId)
return Ok(await _userRoleRepo.DeleteAllUserRoles(projectId));
}

/// <summary> Returns whether current user has specified permission in current project </summary>
[HttpPost("permission", Name = "HasPermission")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(bool))]
public async Task<IActionResult> HasPermission(string projectId, [FromBody, BindRequired] Permission perm)
{
return Ok(await _permissionService.HasProjectPermission(HttpContext, perm, projectId));
}

/// <summary> Returns <see cref="UserRole"/> with specified id </summary>
[HttpGet("current", Name = "GetCurrentPermissions")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(List<Permission>))]
Expand Down
77 changes: 77 additions & 0 deletions src/api/api/user-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,42 @@ export const UserApiAxiosParamCreator = function (
options: localVarRequestOptions,
};
},
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
isUserSiteAdmin: async (options: any = {}): Promise<RequestArgs> => {
const localVarPath = `/v1/users/issiteadmin`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}

const localVarRequestOptions = {
method: "GET",
...baseOptions,
...options,
};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;

setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
let headersFromBaseOptions =
baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {
...localVarHeaderParameter,
...headersFromBaseOptions,
...options.headers,
};

return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {PasswordResetData} passwordResetData
Expand Down Expand Up @@ -735,6 +771,25 @@ export const UserApiFp = function (configuration?: Configuration) {
configuration
);
},
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async isUserSiteAdmin(
options?: any
): Promise<
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<boolean>
> {
const localVarAxiosArgs =
await localVarAxiosParamCreator.isUserSiteAdmin(options);
return createRequestFunction(
localVarAxiosArgs,
globalAxios,
BASE_PATH,
configuration
);
},
/**
*
* @param {PasswordResetData} passwordResetData
Expand Down Expand Up @@ -919,6 +974,16 @@ export const UserApiFactory = function (
.isEmailUnavailable(email, options)
.then((request) => request(axios, basePath));
},
/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
isUserSiteAdmin(options?: any): AxiosPromise<boolean> {
return localVarFp
.isUserSiteAdmin(options)
.then((request) => request(axios, basePath));
},
/**
*
* @param {PasswordResetData} passwordResetData
Expand Down Expand Up @@ -1236,6 +1301,18 @@ export class UserApi extends BaseAPI {
.then((request) => request(this.axios, this.basePath));
}

/**
*
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof UserApi
*/
public isUserSiteAdmin(options?: any) {
return UserApiFp(this.configuration)
.isUserSiteAdmin(options)
.then((request) => request(this.axios, this.basePath));
}

/**
*
* @param {UserApiResetPasswordRequest} requestParameters Request parameters.
Expand Down
140 changes: 140 additions & 0 deletions src/api/api/user-role-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,63 @@ export const UserRoleApiAxiosParamCreator = function (
options: localVarRequestOptions,
};
},
/**
*
* @param {string} projectId
* @param {string} body
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
hasPermission: async (
projectId: string,
body: string,
options: any = {}
): Promise<RequestArgs> => {
// verify required parameter 'projectId' is not null or undefined
assertParamExists("hasPermission", "projectId", projectId);
// verify required parameter 'body' is not null or undefined
assertParamExists("hasPermission", "body", body);
const localVarPath =
`/v1/projects/{projectId}/userroles/permission`.replace(
`{${"projectId"}}`,
encodeURIComponent(String(projectId))
);
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}

const localVarRequestOptions = {
method: "POST",
...baseOptions,
...options,
};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;

localVarHeaderParameter["Content-Type"] = "application/json";

setSearchParams(localVarUrlObj, localVarQueryParameter, options.query);
let headersFromBaseOptions =
baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {
...localVarHeaderParameter,
...headersFromBaseOptions,
...options.headers,
};
localVarRequestOptions.data = serializeDataIfNeeded(
body,
localVarRequestOptions,
configuration
);

return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
/**
*
* @param {string} userId
Expand Down Expand Up @@ -485,6 +542,32 @@ export const UserRoleApiFp = function (configuration?: Configuration) {
configuration
);
},
/**
*
* @param {string} projectId
* @param {string} body
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async hasPermission(
projectId: string,
body: string,
options?: any
): Promise<
(axios?: AxiosInstance, basePath?: string) => AxiosPromise<boolean>
> {
const localVarAxiosArgs = await localVarAxiosParamCreator.hasPermission(
projectId,
body,
options
);
return createRequestFunction(
localVarAxiosArgs,
globalAxios,
BASE_PATH,
configuration
);
},
/**
*
* @param {string} userId
Expand Down Expand Up @@ -602,6 +685,22 @@ export const UserRoleApiFactory = function (
.getProjectUserRoles(projectId, options)
.then((request) => request(axios, basePath));
},
/**
*
* @param {string} projectId
* @param {string} body
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
hasPermission(
projectId: string,
body: string,
options?: any
): AxiosPromise<boolean> {
return localVarFp
.hasPermission(projectId, body, options)
.then((request) => request(axios, basePath));
},
/**
*
* @param {string} userId
Expand Down Expand Up @@ -707,6 +806,27 @@ export interface UserRoleApiGetProjectUserRolesRequest {
readonly projectId: string;
}

/**
* Request parameters for hasPermission operation in UserRoleApi.
* @export
* @interface UserRoleApiHasPermissionRequest
*/
export interface UserRoleApiHasPermissionRequest {
/**
*
* @type {string}
* @memberof UserRoleApiHasPermission
*/
readonly projectId: string;

/**
*
* @type {string}
* @memberof UserRoleApiHasPermission
*/
readonly body: string;
}

/**
* Request parameters for updateUserRole operation in UserRoleApi.
* @export
Expand Down Expand Up @@ -830,6 +950,26 @@ export class UserRoleApi extends BaseAPI {
.then((request) => request(this.axios, this.basePath));
}

/**
*
* @param {UserRoleApiHasPermissionRequest} requestParameters Request parameters.
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof UserRoleApi
*/
public hasPermission(
requestParameters: UserRoleApiHasPermissionRequest,
options?: any
) {
return UserRoleApiFp(this.configuration)
.hasPermission(
requestParameters.projectId,
requestParameters.body,
options
)
.then((request) => request(this.axios, this.basePath));
}

/**
*
* @param {UserRoleApiUpdateUserRoleRequest} requestParameters Request parameters.
Expand Down
9 changes: 9 additions & 0 deletions src/backend/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -590,6 +590,10 @@ export async function deleteUser(userId: string): Promise<string> {
return (await userApi.deleteUser({ userId }, defaultOptions())).data;
}

export async function isSiteAdmin(): Promise<boolean> {
return (await userApi.isUserSiteAdmin(defaultOptions())).data;
}

/* UserEditController.cs */

/** Returns guid of added goal, or of updated goal
Expand Down Expand Up @@ -656,6 +660,11 @@ export async function getCurrentPermissions(): Promise<Permission[]> {
.data;
}

export async function hasPermission(perm: Permission): Promise<boolean> {
const params = { body: perm, projectId: LocalStorage.getProjectId() };
return (await userRoleApi.hasPermission(params, defaultOptions())).data;
}

export async function addOrUpdateUserRole(
projectId: string,
role: Role,
Expand Down
12 changes: 2 additions & 10 deletions src/components/AppBar/ProjectButtons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import { useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";

import { Permission } from "api/models";
import { getCurrentPermissions } from "backend";
import { getCurrentUser } from "backend/localStorage";
import { hasPermission } from "backend";
import {
TabProps,
buttonMinHeight,
Expand All @@ -26,13 +25,6 @@ const enum projNameLength {
xl = 51,
}

export async function getHasStatsPermission(): Promise<boolean> {
if (getCurrentUser()?.isAdmin) {
return true;
}
return (await getCurrentPermissions()).includes(Permission.Statistics);
}

/** A button that redirects to the project settings */
export default function ProjectButtons(props: TabProps): ReactElement {
const projectName = useSelector(
Expand All @@ -43,7 +35,7 @@ export default function ProjectButtons(props: TabProps): ReactElement {
const navigate = useNavigate();

useEffect(() => {
getHasStatsPermission().then(setHasStatsPermission);
hasPermission(Permission.Statistics).then(setHasStatsPermission);
}, []);

return (
Expand Down
Loading

0 comments on commit bdef32d

Please sign in to comment.