Skip to content

Commit

Permalink
User management login allowed (#8433)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jan authored Feb 23, 2023
1 parent 1af5150 commit 156d02a
Show file tree
Hide file tree
Showing 23 changed files with 274 additions and 28 deletions.
2 changes: 1 addition & 1 deletion .drone.env
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# The version of OCIS to use in pipelines that test against OCIS
OCIS_COMMITID=359509dcfe0bd8bf23c71c7aee5fdd27faa7675a
OCIS_COMMITID=9e73b17a403a9283a281495bb3c35246c75c08e9
OCIS_BRANCH=master
8 changes: 8 additions & 0 deletions changelog/unreleased/enhancement-user-settings-login-field
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Enhancement: User settings login field

We've introduced the new login field in the user settings,
where the admin can allow or disallow the login for the respective user.

https://github.com/owncloud/web/pull/8433
https://github.com/owncloud/web/issues/8484
https://github.com/owncloud/web/issues/8467
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Enhancement: Align disabled state on oc-select component

We've align the visual disabled state on the oc-select component,
so it unifies with the disabled state of the oc-text-input component.
4 changes: 3 additions & 1 deletion packages/design-system/src/components/OcSelect/OcSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,9 @@ export default defineComponent({
.vs__open-indicator,
.vs__search,
.vs__selected {
background-color: var(--oc-color-input-bg) !important;
background-color: var(--oc-color-background-muted) !important;
color: var(--oc-color-text-muted) !important;
cursor: default;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,11 @@ export default defineComponent({
return this.$gettext('%{groupCount} matching groups', { groupCount: this.data.length })
},
data() {
const orderedGroups = this.orderBy(this.groups, this.sortBy, this.sortDir === 'desc')
return this.filter(orderedGroups, this.filterTerm)
return this.orderBy(
this.filter(this.groups, this.filterTerm),
this.sortBy,
this.sortDir === 'desc'
)
},
highlighted() {
return this.selectedGroups.map((group) => group.id)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ export default defineComponent({
})
}
const orderedSpaces = computed(() =>
filter(orderBy(props.spaces, unref(sortBy), unref(sortDir) === 'desc'), unref(filterTerm))
orderBy(filter(props.spaces, unref(filterTerm)), unref(sortBy), unref(sortDir) === 'desc')
)
const handleSort = (event) => {
sortBy.value = event.sortBy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@
<span v-if="user.appRoleAssignments" v-text="roleDisplayName" />
</td>
</tr>
<tr>
<th scope="col" class="oc-pr-s" v-text="$gettext('Login')" />
<td>
<span v-text="loginDisplayValue" />
</td>
</tr>
<tr>
<th scope="col" class="oc-pr-s" v-text="$gettext('Quota')" />
<td>
Expand Down Expand Up @@ -129,6 +135,11 @@ export default defineComponent({
return this.user.drive.quota.total === 0
? this.$gettext('No restriction')
: formatFileSize(this.user.drive.quota.total, this.currentLanguage)
},
loginDisplayValue() {
return this.user.accountEnabled === false
? this.$gettext('Forbidden')
: this.$gettext('Allowed')
}
}
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,22 @@
</oc-select>
<div class="oc-text-input-message"></div>
</div>
<div class="oc-mb-s">
<oc-select
id="login-input"
:disabled="isLoginInputDisabled"
:model-value="editUser"
:label="$gettext('Login')"
:options="loginOptions"
:clearable="false"
@update:model-value="onUpdateLogin"
>
<template #selected-option>
{{ selectedLoginLabel }}
</template>
</oc-select>
<div class="oc-text-input-message"></div>
</div>
<quota-select
v-if="showQuota"
:key="'quota-select-' + user.id"
Expand Down Expand Up @@ -92,7 +108,7 @@ import GroupSelect from './GroupSelect.vue'
import QuotaSelect from 'web-pkg/src/components/QuotaSelect.vue'
import { cloneDeep } from 'lodash-es'
import { Group, User } from 'web-client/src/generated'
import { MaybeRef, useGraphClient } from 'web-pkg'
import { MaybeRef, useGraphClient, useStore } from 'web-pkg'
export default defineComponent({
name: 'EditPanel',
Expand All @@ -118,6 +134,8 @@ export default defineComponent({
},
emits: ['confirm'],
setup(props) {
const store = useStore()
const currentUser = store.getters.user
const editUser: MaybeRef<User> = ref({})
const formData = ref({
displayName: {
Expand All @@ -133,16 +151,36 @@ export default defineComponent({
valid: true
}
})
const groupOptions = computed(() => {
const { memberOf: selectedGroups } = unref(editUser)
return props.groups
.filter((g) => !selectedGroups.some((s) => s.id === g.id))
.sort((a, b) => a.displayName.localeCompare(b.displayName))
})
return { editUser, formData, groupOptions, ...useGraphClient() }
const isLoginInputDisabled = computed(() => currentUser.uuid === (props.user as User).id)
return { isLoginInputDisabled, editUser, formData, groupOptions, ...useGraphClient() }
},
computed: {
loginOptions() {
return [
{
label: this.$gettext('Allowed'),
value: true
},
{
label: this.$gettext('Forbidden'),
value: false
}
]
},
selectedLoginLabel() {
return this.editUser.accountEnabled === false
? this.$gettext('Forbidden')
: this.$gettext('Allowed')
},
translatedRoleOptions() {
return this.roles.map((role) => {
return { ...role, displayName: this.$gettext(role.displayName) }
Expand Down Expand Up @@ -173,6 +211,19 @@ export default defineComponent({
},
deep: true,
immediate: true
},
editUser: {
handler: function () {
/**
* Property accountEnabled won't be always set, but this still means, that login is allowed.
* So we actually don't need to change the property if missing and not set to forbidden in the UI.
* This also avoids the compare save dialog from displaying that there are unsaved changes.
*/
if (this.editUser.accountEnabled === true && this.user.accountEnabled !== false) {
delete this.editUser.accountEnabled
}
},
deep: true
}
},
methods: {
Expand Down Expand Up @@ -261,6 +312,9 @@ export default defineComponent({
this.editUser.passwordProfile = {
password
}
},
onUpdateLogin({ value }) {
this.editUser.accountEnabled = value
}
}
})
Expand Down
43 changes: 35 additions & 8 deletions packages/web-app-admin-settings/src/components/Users/UsersList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,18 @@
<template #role="{ item }">
<template v-if="item.appRoleAssignments">{{ getRoleDisplayNameByUser(item) }}</template>
</template>
<template #accountEnabled="{ item }">
<span v-if="item.accountEnabled === false" class="oc-flex oc-flex-middle">
<oc-icon name="stop-circle" fill-type="line" class="oc-mr-s" /><span
v-text="$gettext('Forbidden')"
/>
</span>
<span v-else class="oc-flex oc-flex-middle">
<oc-icon name="play-circle" fill-type="line" class="oc-mr-s" /><span
v-text="$gettext('Allowed')"
/>
</span>
</template>
<template #actions="{ item }">
<oc-button
v-oc-tooltip="$gettext('Details')"
Expand Down Expand Up @@ -242,6 +254,12 @@ export default defineComponent({
type: 'slot',
sortable: true
},
{
name: 'accountEnabled',
title: this.$gettext('Login'),
type: 'slot',
sortable: true
},
{
name: 'actions',
title: this.$gettext('Actions'),
Expand All @@ -252,8 +270,11 @@ export default defineComponent({
]
},
data() {
const orderedUsers = this.orderBy(this.users, this.sortBy, this.sortDir === 'desc')
return this.filter(orderedUsers, this.filterTerm)
return this.orderBy(
this.filter(this.users, this.filterTerm),
this.sortBy,
this.sortDir === 'desc'
)
},
highlighted() {
return this.selectedUsers.map((user) => user.id)
Expand Down Expand Up @@ -290,12 +311,18 @@ export default defineComponent({
return [...list].sort((user1, user2) => {
let a, b
if (prop === 'role') {
a = this.getRoleDisplayNameByUser(user1)
b = this.getRoleDisplayNameByUser(user2)
} else {
a = user1[prop] || ''
b = user2[prop] || ''
switch (prop) {
case 'role':
a = this.getRoleDisplayNameByUser(user1)
b = this.getRoleDisplayNameByUser(user2)
break
case 'accountEnabled':
a = ('accountEnabled' in user1 ? user1.accountEnabled : true).toString()
b = ('accountEnabled' in user2 ? user2.accountEnabled : true).toString()
break
default:
a = user1[prop] || ''
b = user2[prop] || ''
}
return desc ? b.localeCompare(a) : a.localeCompare(b)
Expand Down
3 changes: 2 additions & 1 deletion packages/web-app-admin-settings/src/views/Users.vue
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ export default defineComponent({
const { data } = yield unref(graphClient).users.getUser(user.id)
unref(additionalUserDataLoadedForUserIds).push(user.id)
Object.assign(user, data)
})
Expand Down Expand Up @@ -435,8 +436,8 @@ export default defineComponent({
}
const { data: updatedUser } = await this.graphClient.users.getUser(user.id)
const userIndex = this.users.findIndex((user) => user.id === updatedUser.id)
this.users[userIndex] = updatedUser
eventBus.publish('sidebar.entity.saved')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ exports[`EditPanel renders all available inputs 1`] = `
<oc-select-stub clearable="false" disabled="false" filter="[Function]" id="oc-select-5" label="Role" loading="false" model-value="[object Object]" optionlabel="displayName" options="[object Object]" searchable="true"></oc-select-stub>
<div class="oc-text-input-message"></div>
</div>
<div class="oc-mb-s">
<oc-select-stub clearable="false" disabled="false" filter="[Function]" id="login-input" label="Login" loading="false" model-value="[object Object]" options="[object Object],[object Object]" searchable="true"></oc-select-stub>
<div class="oc-text-input-message"></div>
</div>
<quota-select-stub class="oc-mb-s" maxquota="0" title="Personal quota" totalquota="0"></quota-select-stub>
<group-select-stub class="oc-mb-s" groupoptions="undefined,undefined" selectedgroups=""></group-select-stub>
</div>
Expand Down
11 changes: 1 addition & 10 deletions pnpm-lock.yaml

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
Feature: spaces management

Scenario: user login can be managed in the admin settings
Given "Admin" creates following users
| id |
| Alice |
When "Admin" logs in
And "Admin" opens the "admin-settings" app
And "Admin" navigates to the users management page
And "Admin" forbids the login for the following user "Alice" using the sidebar panel
And "Admin" logs out
Then "Alice" fails to log in
When "Admin" logs in
And "Admin" opens the "admin-settings" app
And "Admin" navigates to the users management page
And "Admin" allows the login for the following user "Alice" using the sidebar panel
And "Admin" logs out
Then "Alice" logs in
And "Alice" logs out
28 changes: 28 additions & 0 deletions tests/e2e/cucumber/steps/ui/adminSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,31 @@ When(
}
}
)

When(
'{string} navigates to the users management page',
async function (this: World, stepUser: string): Promise<void> {
const { page } = this.actorsEnvironment.getActor({ key: stepUser })
const pageObject = new objects.applicationAdminSettings.page.Users({ page })
await pageObject.navigate()
}
)

When(
/^"([^"]*)" (allows|forbids) the login for the following user "([^"]*)" using the sidebar panel$/,
async function (this: World, stepUser: string, action: string, key: string): Promise<void> {
const { page } = this.actorsEnvironment.getActor({ key: stepUser })
const usersObject = new objects.applicationAdminSettings.Users({ page })

switch (action) {
case 'allows':
await usersObject.allowLogin({ key })
break
case 'forbids':
await usersObject.forbidLogin({ key })
break
default:
throw new Error(`${action} not implemented`)
}
}
)
Loading

0 comments on commit 156d02a

Please sign in to comment.