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

Add expiration for space members and apply new permission structure #8320

Merged
merged 11 commits into from
Jan 31, 2023
6 changes: 6 additions & 0 deletions changelog/unreleased/enhancement-space-member-expiration
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Enhancement: Space member expiration

Space member shares now support expiration.

https://github.com/owncloud/web/pull/8320
https://github.com/owncloud/web/issues/8328
9 changes: 9 additions & 0 deletions changelog/unreleased/enhancement-update-sdk
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
Enhancement: Update SDK to v3.1.0-alpha.2

We updated the ownCloud SDK to version v3.1.0-alpha.2. Please refer to the full changelog in the SDK release (linked) for more details. Summary:

* Bugfix - Allow removing expiration dates from space shares: [#1204](https://github.com/owncloud/owncloud-sdk/pull/1204)
* Enhancement - Resource processing: [#1109](https://github.com/owncloud/owncloud-sdk/pull/1109)

https://github.com/owncloud/web/pull/8320
https://github.com/owncloud/owncloud-sdk/releases/tag/v3.1.0-alpha.2
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
@optionChange="collaboratorRoleChanged"
/>
<expiration-datepicker
v-if="!saving"
:share-types="selectedCollaborators.map((c) => c.value.shareType)"
@optionChange="collaboratorExpiryChanged"
/>
Expand Down
1 change: 1 addition & 0 deletions packages/web-client/src/helpers/resource/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { User } from '../user'
export interface SpaceRole {
id: string
displayName: string
expirationDate: string
kind: 'user' | 'group'
isMember(u: User): boolean
}
Expand Down
4 changes: 3 additions & 1 deletion packages/web-client/src/helpers/share/space.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export function buildSpaceShare(s, storageId): Share {
additionalInfo: null
},
permissions,
role
role,
expiration: s.expirationDate,
expires: s.expirationDate ? new Date(s.expirationDate) : undefined
}
}
47 changes: 23 additions & 24 deletions packages/web-client/src/helpers/space/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,33 +85,32 @@ export function buildSpace(data): SpaceResource {

if (data.root?.permissions) {
for (const permission of data.root.permissions) {
for (const role of SpacePeopleShareRoles.list()) {
if (permission.roles.includes(role.name)) {
spaceRoles[role.name] = permission.grantedToIdentities.reduce((acc, info) => {
const kind = info.hasOwnProperty('group') ? 'group' : 'user'
const spaceRole: SpaceRole = {
kind,
id: info[kind].id,
displayName: info[kind].displayName,
isMember(u?: any): boolean {
if (!u) {
return false
}
spaceRoles[permission.roles[0]].push(
...permission.grantedToIdentities.reduce((acc, info) => {
const kind = info.hasOwnProperty('group') ? 'group' : 'user'
const spaceRole: SpaceRole = {
kind,
id: info[kind].id,
displayName: info[kind].displayName,
expirationDate: permission.expirationDateTime,
isMember(u?: any): boolean {
if (!u) {
return false
}

switch (this.kind) {
case 'user':
return this.id == u.uuid
case 'group':
return u.groups.map((g) => g.id).includes(this.id)
default:
return false
}
switch (this.kind) {
case 'user':
return this.id == u.uuid
case 'group':
return u.groups.map((g) => g.id).includes(this.id)
default:
return false
}
}
return [...acc, spaceRole]
}, [])
}
}
}
return [...acc, spaceRole]
}, [])
)
}

if (data.root?.deleted) {
Expand Down
32 changes: 32 additions & 0 deletions packages/web-client/tests/unit/helpers/share/space.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {
buildSpaceShare,
spaceRoleEditor,
spaceRoleManager,
spaceRoleViewer
} from '../../../../src/helpers/share'

const getRawShareData = ({ role = spaceRoleManager.name, expirationDate = undefined }) => ({
role,
onPremisesSamAccountName: 'einstein',
displayName: 'Albert Einstein',
expirationDate
})

describe('buildSpaceShare', () => {
it.each([spaceRoleManager, spaceRoleEditor, spaceRoleViewer])(
'properly assigns space roles',
(role) => {
const share = buildSpaceShare(getRawShareData({ role: role.name }), 1)
expect(share.permissions).toEqual(role.bitmask(true))
expect(share.role).toEqual(role)
}
)
it.each([undefined, '2007-08-31T16:47+00:00'])(
'properly assigns expirationDate',
(expirationDate) => {
const share = buildSpaceShare(getRawShareData({ expirationDate }), 1)
expect(share.expiration).toEqual(expirationDate)
expect(share.expires).toEqual(expirationDate ? new Date(expirationDate) : undefined)
}
)
})
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('buildSpace', () => {
])('returns true for a viewer of the space', (data) => {
const space = buildSpace({
root: {
permissions: [{ roles: data.role, grantedToIdentities: [{ user: { id: uuid } }] }]
permissions: [{ roles: [data.role], grantedToIdentities: [{ user: { id: uuid } }] }]
}
}) as ProjectSpaceResource
expect(space.isViewer(mock<User>({ uuid }))).toBe(data.expectedResult)
Expand All @@ -29,7 +29,7 @@ describe('buildSpace', () => {
])('returns true for a editor of the space', (data) => {
const space = buildSpace({
root: {
permissions: [{ roles: data.role, grantedToIdentities: [{ user: { id: uuid } }] }]
permissions: [{ roles: [data.role], grantedToIdentities: [{ user: { id: uuid } }] }]
}
}) as ProjectSpaceResource
expect(space.isEditor(mock<User>({ uuid }))).toBe(data.expectedResult)
Expand All @@ -44,7 +44,7 @@ describe('buildSpace', () => {
])('returns true for a manager of the space', (data) => {
const space = buildSpace({
root: {
permissions: [{ roles: data.role, grantedToIdentities: [{ user: { id: uuid } }] }]
permissions: [{ roles: [data.role], grantedToIdentities: [{ user: { id: uuid } }] }]
}
}) as ProjectSpaceResource
expect(space.isManager(mock<User>({ uuid }))).toBe(data.expectedResult)
Expand Down
2 changes: 1 addition & 1 deletion packages/web-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"luxon": "^2.4.0",
"marked": "^4.0.12",
"oidc-client-ts": "^2.1.0",
"owncloud-sdk": "~3.1.0-alpha.1",
"owncloud-sdk": "~3.1.0-alpha.2",
"p-queue": "^6.6.2",
"popper-max-size-modifier": "^0.2.0",
"portal-vue": "3.0.0",
Expand Down
37 changes: 29 additions & 8 deletions packages/web-runtime/src/store/spaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ const actions = {
const spaceShares = []

for (const role of Object.keys(space.spaceRoles)) {
for (const { kind, id } of space.spaceRoles[role]) {
for (const { kind, id, expirationDate } of space.spaceRoles[role]) {
const client = unref(graphClient)
let prom: Promise<AxiosResponse>
switch (kind) {
Expand All @@ -161,7 +161,7 @@ const actions = {
}

prom.then((resolved) => {
spaceShares.push(buildSpaceShare({ ...resolved.data, role }, space.id))
spaceShares.push(buildSpaceShare({ ...resolved.data, role, expirationDate }, space.id))
})

promises.push(prom)
Expand All @@ -174,25 +174,45 @@ const actions = {
},
async addSpaceMember(
context,
{ client, graphClient, path, shareWith, permissions, role, storageId, displayName }
{
client,
graphClient,
path,
shareWith,
permissions,
role,
storageId,
displayName,
expirationDate
}
) {
await client.shares.shareSpaceWithUser(path, shareWith, storageId, {
permissions,
role: role.name
role: role.name,
expirationDate
})
const graphResponse = await graphClient.drives.getDrive(storageId)
context.commit('UPSERT_SPACE', buildSpace(graphResponse.data))
const shareObj = { role: role.name, onPremisesSamAccountName: shareWith, displayName }
const shareObj = {
role: role.name,
onPremisesSamAccountName: shareWith,
displayName,
expirationDate
}
context.commit('UPSERT_SPACE_MEMBERS', buildSpaceShare(shareObj, storageId))
},
async changeSpaceMember(context, { client, graphClient, share, permissions, role }) {
async changeSpaceMember(
context,
{ client, graphClient, share, permissions, expirationDate, role }
) {
await client.shares.shareSpaceWithUser(
'',
share.collaborator.name || share.collaborator.displayName,
share.id,
{
permissions,
role: role.name
role: role.name,
expirationDate
}
)

Expand All @@ -202,7 +222,8 @@ const actions = {
{
role: role.name,
onPremisesSamAccountName: share.collaborator.name,
displayName: share.collaborator.displayName
displayName: share.collaborator.displayName,
expirationDate
},
share.id
)
Expand Down
8 changes: 4 additions & 4 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

30 changes: 30 additions & 0 deletions tests/e2e/cucumber/features/smoke/spaces/memberExpiry.ocis.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Feature: spaces member expiry

# FIXME: Un-skip once https://github.com/owncloud/ocis/pull/5453 has been merged
# Scenario: space members can be invited with an expiration date
# Given "Admin" creates following users
# | id |
# | Alice |
# | Brian |
# And "Admin" assigns following roles to the users
# | id | role |
# | Alice | SpaceAdmin |
# And "Alice" creates the following project space using API
# | name | id |
# | team | team.1 |
# When "Alice" logs in
# And "Alice" opens the "files" app
# And "Alice" navigates to the projects space page
# And "Alice" navigates to the project space "team.1"
# And "Alice" adds following users to the project space
# | user | role | kind |
# | Brian | editor | user |
# And "Alice" sets the expiration date of the member "Brian" of the project space to "+5 days"
# When "Brian" logs in
# And "Brian" navigates to the projects space page
# And "Brian" navigates to the project space "team.1"
# And "Brian" logs out
# When "Alice" navigates to the projects space page
# And "Alice" navigates to the project space "team.1"
# And "Alice" removes the expiration date of the member "Brian" of the project space
# And "Alice" logs out
25 changes: 25 additions & 0 deletions tests/e2e/cucumber/steps/ui/spaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,3 +186,28 @@ When(
})
}
)

When(
'{string} sets the expiration date of the member {string} of the project space to {string}',
async function (
this: World,
stepUser: string,
memberName: string,
expirationDate: string
): Promise<void> {
const { page } = this.actorsEnvironment.getActor({ key: stepUser })
const spacesObject = new objects.applicationFiles.Spaces({ page })
const member = { collaborator: this.usersEnvironment.getUser({ key: memberName }) }
await spacesObject.addExpirationDate({ member, expirationDate })
}
)

When(
'{string} removes the expiration date of the member {string} of the project space',
async function (this: World, stepUser: string, memberName: string): Promise<void> {
const { page } = this.actorsEnvironment.getActor({ key: stepUser })
const spacesObject = new objects.applicationFiles.Spaces({ page })
const member = { collaborator: this.usersEnvironment.getUser({ key: memberName }) }
await spacesObject.removeExpirationDate({ member })
}
)
Loading