Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Permissions: More granular access and Project Access page #877

Open
wants to merge 36 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5b9dfb8
fix: show project level access groups
martastain Oct 16, 2024
facace6
generating user permissions endpoints
flynput Oct 16, 2024
0b0a55b
feat: project permissions p.o.c.
martastain Oct 16, 2024
bfffcf7
feat(settings): add permissions widget
martastain Oct 17, 2024
229fa04
feat(settings): allow hide disabled groups using hideDisabledGroups c…
martastain Oct 17, 2024
8fb808a
chore: hide disabled groups in accessgroup editor
martastain Oct 17, 2024
ed4b0ca
feat: limit project management pages p.o.c.
martastain Oct 17, 2024
ee5c4c8
feature(Users): Limiting user access to project settings module based…
flynput Oct 22, 2024
00f661e
feature(Users): Adding better placeholder messages when lacking view …
flynput Oct 24, 2024
dac1a86
wip
flynput Oct 24, 2024
251a48f
wip
flynput Oct 29, 2024
035312a
feature(Users): Updated Project users access groups page
flynput Nov 5, 2024
03252b1
wip - mutations added
flynput Nov 5, 2024
925804f
feature(Users): Fetching & filtering users based on project & assigne…
flynput Nov 6, 2024
3d88c42
feature(Users): UI redesign, implemented user panels hover & select b…
flynput Nov 7, 2024
1e2bc57
feature(Users): Extracting logic to hook, added remove handler
flynput Nov 7, 2024
dbe12e6
feature(Users): Using add handler for users already asssigned groups …
flynput Nov 7, 2024
967ecd4
feature(Users): Updated add/remove logic UI & payload logic, minor UI…
flynput Nov 8, 2024
8cf585a
feature(Users): Re-enabled multiple users remove action
flynput Nov 8, 2024
1b47bbd
feature(Users): Added filtering for projects & users
flynput Nov 8, 2024
db46674
feature(Users): Filtering access groups also, respecting filtering wh…
flynput Nov 11, 2024
cface9e
feature(Users): User Permissions class refactoring, removed gap in pr…
flynput Nov 11, 2024
bfef113
feature(Users): Fetching user access data for multiple projects in cu…
flynput Nov 11, 2024
28b545d
feature(Users): Fixing invalidation/cache update issues
flynput Nov 11, 2024
768c349
feature(Users): Refactoring permissions module, considering all proje…
flynput Nov 12, 2024
735515e
feature(Users): UI/UX tweaks and fixes
flynput Nov 12, 2024
a73b3b0
feature(Users): Enabled fuzzy filtering for projects and access group…
flynput Nov 13, 2024
3ce5014
feature(Users): Added context menu for unassigned users pane, other s…
flynput Nov 13, 2024
76e44cb
feature(Users): Added context menu for already assigned users
flynput Nov 13, 2024
6c694be
feature(Users): Sorting, marking as inactive projects based on permis…
flynput Nov 13, 2024
466a6e1
feature(Users): Filtering add/remove actions depending on selected pr…
flynput Nov 14, 2024
29e9f83
feature(Users): Adding hover key shortcuts, removing global anatomy s…
flynput Nov 14, 2024
5324060
fix: project access overflow issues
Innders Nov 14, 2024
08d5227
Merge branch '872-ay-5074-top-level-permissions-for-normal-users' of …
flynput Nov 14, 2024
6dd3ca8
fix(Editor): Persisting filters, UI/UX tweaks, various fixes
flynput Nov 15, 2024
fd5898c
fix(Editor): Consistent search filtering between filter component and…
flynput Nov 15, 2024
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
2 changes: 1 addition & 1 deletion gen/openapi-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const outputFiles = {
market: ['marketAddonList', 'marketAddonDetail', 'marketAddonVersionDetail'],
watchers: ['getEntityWatchers', 'setEntityWatchers'],
inbox: ['manageInboxItem'],
project: ['getProject', 'listProjects', 'getProjectAnatomy'],
project: ['getProject', 'listProjects', 'getProjectAnatomy', 'getProjectUsers'],
review: [
'getReviewablesForVersion',
'getReviewablesForProduct',
Expand Down
89 changes: 89 additions & 0 deletions src/api/rest/permissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { RestAPI as api } from '../../services/ayon'
const injectedRtkApi = api.injectEndpoints({
endpoints: (build) => ({
getCurrentUserPermissions: build.query<
GetCurrentUserPermissionsApiResponse,
GetCurrentUserPermissionsApiArg
>({
query: () => ({ url: `/api/users/me/permissions` }),
}),
getCurrentUserProjectPermissions: build.query<
GetCurrentUserProjectPermissionsApiResponse,
GetCurrentUserProjectPermissionsApiArg
>({
query: (queryArg) => ({ url: `/api/users/me/permissions/${queryArg.projectName}` }),
}),
}),
overrideExisting: false,
})
export { injectedRtkApi as api }
export type GetCurrentUserPermissionsApiResponse = /** status 200 Successful Response */ any
export type GetCurrentUserPermissionsApiArg = void
export type GetCurrentUserProjectPermissionsApiResponse =
/** status 200 Successful Response */ Permissions
export type GetCurrentUserProjectPermissionsApiArg = {
projectName: string
}
export type ErrorResponse = {
code: number
detail: string
}
export type ValidationError = {
loc: (string | number)[]
msg: string
type: string
}
export type HttpValidationError = {
detail?: ValidationError[]
}
export type StudioSettingsAccessModel = {
enabled?: boolean
/** List of addons a user can access */
addons?: string[]
}
export type ProjectSettingsAccessModel = {
enabled?: boolean
/** List of addons a user can access */
addons?: string[]
/** Allow users to update the project anatomy */
anatomy_update?: boolean
}
export type FolderAccess = {
access_type?: string
/** The path of the folder to allow access to. Required for access_type 'hierarchy and 'children' */
path?: string
}
export type FolderAccessList = {
enabled?: boolean
access_list?: FolderAccess[]
}
export type AttributeAccessList = {
enabled?: boolean
attributes?: string[]
}
export type EndpointsAccessList = {
enabled?: boolean
endpoints?: string[]
}
export type Permissions = {
/** Restrict access to studio settings */
studio_settings?: StudioSettingsAccessModel
/** Restrict write access to project settings */
project_settings?: ProjectSettingsAccessModel
/** Whitelist folders a user can create */
create?: FolderAccessList
/** Whitelist folders a user can read */
read?: FolderAccessList
/** Whitelist folders a user can update */
update?: FolderAccessList
/** Whitelist folders a user can publish to */
publish?: FolderAccessList
/** Whitelist folders a user can delete */
delete?: FolderAccessList
/** Whitelist attributes a user can read */
attrib_read?: AttributeAccessList
/** Whitelist attributes a user can write */
attrib_write?: AttributeAccessList
/** Whitelist REST endpoints a user can access */
endpoints?: EndpointsAccessList
}
47 changes: 34 additions & 13 deletions src/api/rest/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ const injectedRtkApi = api.injectEndpoints({
method: 'HEAD',
}),
}),
getProjectFilePayload: build.query<
GetProjectFilePayloadApiResponse,
GetProjectFilePayloadApiArg
>({
query: (queryArg) => ({
url: `/api/projects/${queryArg.projectName}/files/${queryArg.fileId}/payload`,
}),
}),
getProjectFileThumbnail: build.query<
GetProjectFileThumbnailApiResponse,
GetProjectFileThumbnailApiArg
Expand All @@ -32,12 +40,10 @@ const injectedRtkApi = api.injectEndpoints({
getProjectActivity: build.query<GetProjectActivityApiResponse, GetProjectActivityApiArg>({
query: (queryArg) => ({
url: `/api/projects/${queryArg.projectName}/dashboard/activity`,
params: {
days: queryArg.days,
},
params: { days: queryArg.days },
}),
}),
getProjectUsers: build.query<GetProjectUsersApiResponse, GetProjectUsersApiArg>({
getProjectTeams: build.query<GetProjectTeamsApiResponse, GetProjectTeamsApiArg>({
query: (queryArg) => ({ url: `/api/projects/${queryArg.projectName}/dashboard/users` }),
}),
getProjectAnatomy: build.query<GetProjectAnatomyApiResponse, GetProjectAnatomyApiArg>({
Expand Down Expand Up @@ -72,14 +78,13 @@ const injectedRtkApi = api.injectEndpoints({
getProjectSiteRoots: build.query<GetProjectSiteRootsApiResponse, GetProjectSiteRootsApiArg>({
query: (queryArg) => ({
url: `/api/projects/${queryArg.projectName}/siteRoots`,
headers: {
'x-ayon-site-id': queryArg['x-ayon-site-id'],
},
params: {
platform: queryArg.platform,
},
headers: { 'x-ayon-site-id': queryArg['x-ayon-site-id'] },
params: { platform: queryArg.platform },
}),
}),
getProjectUsers: build.query<GetProjectUsersApiResponse, GetProjectUsersApiArg>({
query: (queryArg) => ({ url: `/api/projects/${queryArg.projectName}/users` }),
}),
getProjectEntityUris: build.mutation<
GetProjectEntityUrisApiResponse,
GetProjectEntityUrisApiArg
Expand All @@ -104,6 +109,11 @@ export type GetProjectFileHeadApiArg = {
fileId: string
projectName: string
}
export type GetProjectFilePayloadApiResponse = /** status 200 Successful Response */ any
export type GetProjectFilePayloadApiArg = {
fileId: string
projectName: string
}
export type GetProjectFileThumbnailApiResponse = /** status 200 Successful Response */ any
export type GetProjectFileThumbnailApiArg = {
fileId: string
Expand All @@ -124,8 +134,9 @@ export type GetProjectActivityApiArg = {
/** Number of days to retrieve activity for */
days?: number
}
export type GetProjectUsersApiResponse = /** status 200 Successful Response */ UsersResponseModel
export type GetProjectUsersApiArg = {
export type GetProjectTeamsApiResponse =
/** status 200 Successful Response */ ProjectTeamsResponseModel
export type GetProjectTeamsApiArg = {
projectName: string
}
export type GetProjectAnatomyApiResponse = /** status 200 Successful Response */ ProjectAnatomy
Expand Down Expand Up @@ -173,6 +184,12 @@ export type GetProjectSiteRootsApiArg = {
/** Site ID may be specified either as a query parameter (`site_id` or `site`) or in a header. */
'x-ayon-site-id'?: string
}
export type GetProjectUsersApiResponse = /** status 200 Successful Response */ {
[key: string]: string[]
}
export type GetProjectUsersApiArg = {
projectName: string
}
export type GetProjectEntityUrisApiResponse = /** status 200 Successful Response */ GetUrisResponse
export type GetProjectEntityUrisApiArg = {
projectName: string
Expand Down Expand Up @@ -238,7 +255,7 @@ export type ActivityResponseModel = {
/** Activity per day normalized to 0-100 */
activity: number[]
}
export type UsersResponseModel = {
export type ProjectTeamsResponseModel = {
/** Number of active team members */
teamSizeActive?: number
/** Total number of team members */
Expand Down Expand Up @@ -299,6 +316,7 @@ export type Templates = {
others?: CustomTemplate[]
}
export type ProjectAttribModel = {
priority?: 'urgent' | 'high' | 'normal' | 'low'
/** Frame rate */
fps?: number
/** Horizontal resolution */
Expand Down Expand Up @@ -346,6 +364,8 @@ export type Status = {
state?: 'not_started' | 'in_progress' | 'done' | 'blocked'
icon?: string
color?: string
/** Limit the status to specific entity types. */
scope?: string[]
original_name?: string
}
export type Tag = {
Expand Down Expand Up @@ -398,6 +418,7 @@ export type LinkTypeModel = {
data?: object
}
export type ProjectAttribModel2 = {
priority?: 'urgent' | 'high' | 'normal' | 'low'
/** Frame rate */
fps?: number
/** Horizontal resolution */
Expand Down
45 changes: 32 additions & 13 deletions src/containers/AddonSettings/AddonSettings.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useMemo } from 'react'
import { useState, useMemo } from 'react'
import { useSelector } from 'react-redux'
import { toast } from 'react-toastify'

Expand Down Expand Up @@ -37,6 +37,8 @@ import { getValueByPath, setValueByPath, sameKeysStructure, compareObjects } fro
import arrayEquals from '@helpers/arrayEquals'
import { cloneDeep } from 'lodash'
import { usePaste } from '@context/pasteContext'
import useUserProjectPermissions from '@hooks/useUserProjectPermissions'
import EmptyPlaceholder from '@components/EmptyPlaceholder/EmptyPlaceholder'

/*
* key is {addonName}|{addonVersion}|{variant}|{siteId}|{projectKey}
Expand All @@ -52,6 +54,7 @@ const isChildPath = (childPath, parentPath) => {
}

const AddonSettings = ({ projectName, showSites = false }) => {
const isUser = useSelector((state) => state.user.data.isUser)
//const navigate = useNavigate()
const [showHelp, setShowHelp] = useState(false)
const [selectedAddons, setSelectedAddons] = useState([])
Expand All @@ -73,6 +76,8 @@ const AddonSettings = ({ projectName, showSites = false }) => {
const [promoteBundle] = usePromoteBundleMutation()
const { requestPaste } = usePaste()

const userPermissions = useUserProjectPermissions(!isUser)

const projectKey = projectName || '_'

const onAddonFocus = ({ addonName, addonVersion, siteId, path }) => {
Expand Down Expand Up @@ -281,8 +286,7 @@ const AddonSettings = ({ projectName, showSites = false }) => {
}

return { ...unpinnedKeys, [addonKey]: addonChanges }
}) // setUnpinnedKeys

}) // setUnpinnedKeys
}
}
}
Expand All @@ -294,15 +298,15 @@ const AddonSettings = ({ projectName, showSites = false }) => {
const onRemoveOverride = async (addon, siteId, path) => {
// Remove a single override for this addon (within current project and variant)
// path is an array of strings
// TODO: Use this to staged unpin.

// TODO: Use this to staged unpin.
// It is not used now because we don't have an information about the original value
//
// const key = `${addon.name}|${addon.version}|${addon.variant}|${siteId || '_'}|${projectKey}`
//
// setChangedKeys((changedKeys) => {
// const keyData = changedKeys[key] || []
//
//
// const index = keyData.findIndex((keyItem) => arrayEquals(keyItem, path))
// if (index === -1) {
// keyData.push(path)
Expand Down Expand Up @@ -502,8 +506,8 @@ const AddonSettings = ({ projectName, showSites = false }) => {
Are you sure you want to push <strong>{bundleName}</strong> to production?
</p>
<p>
This will mark the current staging bundle as production and copy all staging
studio settings and staging projects overrides to production as well.
This will mark the current staging bundle as production and copy all staging studio
settings and staging projects overrides to production as well.
</p>
</>
)
Expand Down Expand Up @@ -627,6 +631,10 @@ const AddonSettings = ({ projectName, showSites = false }) => {
/>
<SaveButton
label="Save Changes"
disabled={!userPermissions.canEditSettings(projectName)}
data-tooltip={
!userPermissions.canEditSettings(projectName) ? "You don't have edit permissions" : undefined
}
onClick={onSave}
active={canCommit}
saving={setAddonSettingsUpdating}
Expand All @@ -641,6 +649,13 @@ const AddonSettings = ({ projectName, showSites = false }) => {
setCurrentSelection(null)
}

if (!userPermissions.canViewSettings(projectName)) {
return <EmptyPlaceholder
icon="settings_alert"
message="You don't have permissions to view the addon settings for this project"
/>
}

return (
<Splitter layout="horizontal" style={{ width: '100%', height: '100%' }}>
<SplitterPanel size={80} style={{ display: 'flex', flexDirection: 'row', gap: 8 }}>
Expand Down Expand Up @@ -698,7 +713,11 @@ const AddonSettings = ({ projectName, showSites = false }) => {
<Section className={showHelp && 'settings-help-visible'}>
{settingsListHeader}
<Section>
<ScrollPanel className="transparent nopad" style={{ flexGrow: 1 }} id="settings-scroll-panel">
<ScrollPanel
className="transparent nopad"
style={{ flexGrow: 1 }}
id="settings-scroll-panel"
>
{selectedAddons
.filter((addon) => !addon.isBroken)
.reverse()
Expand Down Expand Up @@ -761,10 +780,10 @@ const AddonSettings = ({ projectName, showSites = false }) => {
<SplitterPanel size={20}>
<Section wrap style={{ minWidth: 300 }}>
<Toolbar>{commitToolbar}</Toolbar>
<SettingsChangesTable
changes={changedKeys}
unpins={unpinnedKeys}
onRevert={onRevertChange}
<SettingsChangesTable
changes={changedKeys}
unpins={unpinnedKeys}
onRevert={onRevertChange}
/>
{/*}
<ScrollPanel className="transparent nopad" style={{ flexGrow: 1 }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ function ObjectFieldTemplate(props: { id: string } & ObjectFieldTemplateProps) {
</BadgeWrapper>
</>
)
}
} // Root object - show badges and title

titleComponent = props.idSchema.$id === 'root' ? rootTitle : stringTitle

Expand All @@ -263,6 +263,12 @@ function ObjectFieldTemplate(props: { id: string } & ObjectFieldTemplateProps) {
contextMenu(e, contextMenuModel)
}


const hasEnabled = (props?.schema?.properties || {}).hasOwnProperty('enabled')
const isEnabled = props?.formData?.enabled

const disabled = props.formContext.hideDisabledGroups && hasEnabled && !isEnabled

return (
<SettingsPanel
objId={objId}
Expand All @@ -276,6 +282,7 @@ function ObjectFieldTemplate(props: { id: string } & ObjectFieldTemplateProps) {
onContextMenu={onContextMenu}
currentId={props.formContext.currentId}
layout={undefined}
disabled={disabled}
>
{fields}
</SettingsPanel>
Expand Down
2 changes: 1 addition & 1 deletion src/containers/SettingsEditor/SettingsEditor.sass
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ $field-gap: 8px
padding: 0 3px

.form-inline-field-label
flex-basis: 200px
flex-basis: 250px
padding-left: 4px
cursor: pointer
user-select: none
Expand Down
Loading