From b10e9326058193dd65a57fab2d2f05b7b06096e7 Mon Sep 17 00:00:00 2001 From: Sabir Hassan Date: Mon, 4 Jul 2022 19:14:06 +0500 Subject: [PATCH] feat: added get authorizedRoutes api endpoint --- api/public/swagger.yaml | 28 ++++++++ api/src/controllers/info.ts | 19 +++++ api/src/routes/api/info.ts | 10 +++ api/src/utils/getAuthorizedRoutes.ts | 17 +++++ api/src/utils/index.ts | 1 + api/src/utils/validation.ts | 5 +- .../Settings/addPermissionModal.tsx | 70 ++++++++----------- web/src/containers/Settings/permission.tsx | 1 - 8 files changed, 109 insertions(+), 42 deletions(-) create mode 100644 api/src/utils/getAuthorizedRoutes.ts diff --git a/api/public/swagger.yaml b/api/public/swagger.yaml index 4784099..a92bd29 100644 --- a/api/public/swagger.yaml +++ b/api/public/swagger.yaml @@ -548,6 +548,16 @@ components: - runTimes type: object additionalProperties: false + AuthorizedRoutesResponse: + properties: + routes: + items: + type: string + type: array + required: + - routes + type: object + additionalProperties: false ExecuteReturnJsonPayload: properties: _program: @@ -1593,6 +1603,24 @@ paths: - Info security: [] parameters: [] + /SASjsApi/info/authorizedRoutes: + get: + operationId: AuthorizedRoutes + responses: + '200': + description: Ok + content: + application/json: + schema: + $ref: '#/components/schemas/AuthorizedRoutesResponse' + examples: + 'Example 1': + value: {routes: [/AppStream, /SASjsApi/stp/execute]} + summary: 'Get authorized routes.' + tags: + - Info + security: [] + parameters: [] /SASjsApi/session: get: operationId: Session diff --git a/api/src/controllers/info.ts b/api/src/controllers/info.ts index 5c4cf86..08cd1d5 100644 --- a/api/src/controllers/info.ts +++ b/api/src/controllers/info.ts @@ -1,4 +1,8 @@ import { Route, Tags, Example, Get } from 'tsoa' +import { getAuthorizedRoutes } from '../utils' +export interface AuthorizedRoutesResponse { + URIs: string[] +} export interface InfoResponse { mode: string @@ -36,4 +40,19 @@ export class InfoController { } return response } + + /** + * @summary Get authorized routes. + * + */ + @Example({ + URIs: ['/AppStream', '/SASjsApi/stp/execute'] + }) + @Get('/authorizedRoutes') + public authorizedRoutes(): AuthorizedRoutesResponse { + const response = { + URIs: getAuthorizedRoutes() + } + return response + } } diff --git a/api/src/routes/api/info.ts b/api/src/routes/api/info.ts index fb71f66..0b5a587 100644 --- a/api/src/routes/api/info.ts +++ b/api/src/routes/api/info.ts @@ -13,4 +13,14 @@ infoRouter.get('/', async (req, res) => { } }) +infoRouter.get('/authorizedRoutes', async (req, res) => { + const controller = new InfoController() + try { + const response = controller.authorizedRoutes() + res.send(response) + } catch (err: any) { + res.status(403).send(err.toString()) + } +}) + export default infoRouter diff --git a/api/src/utils/getAuthorizedRoutes.ts b/api/src/utils/getAuthorizedRoutes.ts new file mode 100644 index 0000000..7d63b9f --- /dev/null +++ b/api/src/utils/getAuthorizedRoutes.ts @@ -0,0 +1,17 @@ +export const getAuthorizedRoutes = () => { + const streamingApps = Object.keys(process.appStreamConfig) + const streamingAppsRoutes = streamingApps.map((app) => `/AppStream/${app}`) + return [...StaticAuthorizedRoutes, ...streamingAppsRoutes] +} + +const StaticAuthorizedRoutes = [ + '/AppStream', + '/SASjsApi/code/execute', + '/SASjsApi/stp/execute', + '/SASjsApi/drive/deploy', + '/SASjsApi/drive/upload', + '/SASjsApi/drive/file', + '/SASjsApi/drive/folder', + '/SASjsApi/drive/fileTree', + '/SASjsApi/permission' +] diff --git a/api/src/utils/index.ts b/api/src/utils/index.ts index 36e1efc..13bc904 100644 --- a/api/src/utils/index.ts +++ b/api/src/utils/index.ts @@ -8,6 +8,7 @@ export * from './file' export * from './generateAccessToken' export * from './generateAuthCode' export * from './generateRefreshToken' +export * from './getAuthorizedRoutes' export * from './getCertificates' export * from './getDesktopFields' export * from './getPreProgramVariables' diff --git a/api/src/utils/validation.ts b/api/src/utils/validation.ts index cd91e80..0789fa5 100644 --- a/api/src/utils/validation.ts +++ b/api/src/utils/validation.ts @@ -1,5 +1,6 @@ import Joi from 'joi' import { PermissionSetting, PrincipalType } from '../controllers/permission' +import { getAuthorizedRoutes } from './getAuthorizedRoutes' const usernameSchema = Joi.string().lowercase().alphanum().min(3).max(16) const passwordSchema = Joi.string().min(6).max(1024) @@ -88,7 +89,9 @@ export const registerClientValidation = (data: any): Joi.ValidationResult => export const registerPermissionValidation = (data: any): Joi.ValidationResult => Joi.object({ - uri: Joi.string().required(), + uri: Joi.string() + .required() + .valid(...getAuthorizedRoutes()), setting: Joi.string() .required() .valid(...Object.values(PermissionSetting)), diff --git a/web/src/containers/Settings/addPermissionModal.tsx b/web/src/containers/Settings/addPermissionModal.tsx index 1395a85..2a147dc 100644 --- a/web/src/containers/Settings/addPermissionModal.tsx +++ b/web/src/containers/Settings/addPermissionModal.tsx @@ -1,10 +1,4 @@ -import React, { - useState, - useEffect, - useMemo, - Dispatch, - SetStateAction -} from 'react' +import React, { useState, useEffect, Dispatch, SetStateAction } from 'react' import axios from 'axios' import { Button, @@ -13,15 +7,14 @@ import { DialogContent, DialogActions, TextField, - CircularProgress + CircularProgress, + Autocomplete } from '@mui/material' import { styled } from '@mui/material/styles' -import Autocomplete, { createFilterOptions } from '@mui/material/Autocomplete' import { BootstrapDialogTitle } from '../../components/dialogTitle' import { - PermissionResponse, UserResponse, GroupResponse, RegisterPermissionPayload @@ -39,18 +32,16 @@ const BootstrapDialog = styled(Dialog)(({ theme }) => ({ type AddPermissionModalProps = { open: boolean handleOpen: Dispatch> - permissions: PermissionResponse[] addPermission: (addPermissionPayload: RegisterPermissionPayload) => void } -const filter = createFilterOptions() - const AddPermissionModal = ({ open, handleOpen, - permissions, addPermission }: AddPermissionModalProps) => { + const [URIs, setURIs] = useState([]) + const [loadingURIs, setLoadingURIs] = useState(false) const [uri, setUri] = useState() const [principalType, setPrincipalType] = useState('user') const [userPrincipal, setUserPrincipal] = useState() @@ -60,6 +51,23 @@ const AddPermissionModal = ({ const [userPrincipals, setUserPrincipals] = useState([]) const [groupPrincipals, setGroupPrincipals] = useState([]) + useEffect(() => { + setLoadingURIs(true) + axios + .get('/SASjsApi/info/authorizedRoutes') + .then((res: any) => { + if (res.data) { + setURIs(res.data.URIs) + } + }) + .catch((err) => { + console.log(err) + }) + .finally(() => { + setLoadingURIs(false) + }) + }, []) + useEffect(() => { setLoadingPrincipals(true) axios @@ -97,12 +105,6 @@ const AddPermissionModal = ({ addPermission(addPermissionPayload) } - const URIs = useMemo(() => { - return permissions - .map((permission) => permission.uri) - .filter((uri, index, array) => array.indexOf(uri) === index) - }, [permissions]) - const addButtonDisabled = !uri || (principalType === 'user' ? !userPrincipal : !groupPrincipal) @@ -118,29 +120,17 @@ const AddPermissionModal = ({ setUri(newValue)} - filterOptions={(options, params) => { - const filtered = filter(options, params) - - const { inputValue } = params - - const isExisting = options.some( - (option) => inputValue === option + onChange={(event: any, newValue: string) => setUri(newValue)} + renderInput={(params) => + loadingURIs ? ( + + ) : ( + ) - if (inputValue !== '' && !isExisting) { - filtered.push(inputValue) - } - return filtered - }} - selectOnFocus - clearOnBlur - handleHomeEndKeys - options={URIs} - renderOption={(props, option) =>
  • {option}
  • } - freeSolo - renderInput={(params) => } + } />
    diff --git a/web/src/containers/Settings/permission.tsx b/web/src/containers/Settings/permission.tsx index 86f5fc6..81b2180 100644 --- a/web/src/containers/Settings/permission.tsx +++ b/web/src/containers/Settings/permission.tsx @@ -330,7 +330,6 @@ const Permission = () => {