mirror of
https://github.com/advplyr/audiobookshelf.git
synced 2026-07-07 09:51:37 +00:00
Merge e8b845e43c into 6f03467f35
This commit is contained in:
commit
81cf70320b
5 changed files with 115 additions and 2 deletions
|
|
@ -108,6 +108,20 @@
|
|||
<p class="sm:pl-4 pt-2 sm:pt-0 text-sm text-gray-300" v-html="$strings.LabelOpenIDGroupClaimDescription"></p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row mb-4">
|
||||
<div class="w-44 min-w-44">
|
||||
<ui-text-input-with-label ref="openidAdminGroups" v-model="newAuthSettings.authOpenIDAdminGroups" :disabled="savingSettings" :placeholder="'admin'" :label="'Admin Groups'" />
|
||||
</div>
|
||||
<p class="sm:pl-4 pt-2 sm:pt-0 text-sm text-gray-300" v-html="$strings.LabelOpenIDAdminGroupsDescription"></p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row mb-4">
|
||||
<div class="w-44 min-w-44">
|
||||
<ui-text-input-with-label ref="openidGroupDefaultRole" v-model="newAuthSettings.authOpenIDGroupDefaultRole" :disabled="savingSettings" :placeholder="'user'" :label="'Default Group Role'" />
|
||||
</div>
|
||||
<p class="sm:pl-4 pt-2 sm:pt-0 text-sm text-gray-300" v-html="$strings.LabelOpenIDGroupDefaultRoleDescription"></p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row mb-4">
|
||||
<div class="w-44 min-w-44">
|
||||
<ui-text-input-with-label ref="openidAdvancedPermsClaim" v-model="newAuthSettings.authOpenIDAdvancedPermsClaim" :disabled="savingSettings" :placeholder="'abspermissions'" :label="'Advanced Permission Claim'" />
|
||||
|
|
|
|||
|
|
@ -497,9 +497,11 @@
|
|||
"LabelNumberOfBooks": "Number of Books",
|
||||
"LabelNumberOfChapters": "Number of chapters:",
|
||||
"LabelNumberOfEpisodes": "# of Episodes",
|
||||
"LabelOpenIDAdminGroupsDescription": "Comma-separated list of group names from the group claim that should be mapped to the <code>admin</code> role, in addition to the built-in case-insensitive 'admin', 'user', and 'guest' names. Use this when your identity provider's groups are not named to match the application's roles.",
|
||||
"LabelOpenIDAdvancedPermsClaimDescription": "Name of the OpenID claim that contains advanced permissions for user actions within the application which will apply to non-admin roles (<b>if configured</b>). If the claim is missing from the response, access to ABS will be denied. If a single option is missing, it will be treated as <code>false</code>. Ensure the identity provider's claim matches the expected structure:",
|
||||
"LabelOpenIDClaims": "Leave the following options empty to disable advanced group and permissions assignment, automatically assigning 'User' group then.",
|
||||
"LabelOpenIDGroupClaimDescription": "Name of the OpenID claim that contains a list of the user's groups. Commonly referred to as <code>groups</code>. <b>If configured</b>, the application will automatically assign roles based on the user's group memberships, provided that these groups are named case-insensitively 'admin', 'user', or 'guest' in the claim. The claim should contain a list, and if a user belongs to multiple groups, the application will assign the role corresponding to the highest level of access. If no group matches, access will be denied.",
|
||||
"LabelOpenIDGroupDefaultRoleDescription": "Role to assign when none of the user's groups match a known role (<code>admin</code>, <code>user</code>, <code>guest</code>, or an admin group above). Set to <code>user</code> or <code>guest</code> to admit such users with that role instead of denying access. Leave empty to deny access when no group matches.",
|
||||
"LabelOpenRSSFeed": "Open RSS Feed",
|
||||
"LabelOverwrite": "Overwrite",
|
||||
"LabelPaginationPageXOfY": "Page {0} of {1}",
|
||||
|
|
|
|||
|
|
@ -192,10 +192,12 @@ class OidcAuthStrategy {
|
|||
|
||||
if (!userinfo[groupClaimName]) throw new Error(`Group claim ${groupClaimName} not found in userinfo`)
|
||||
|
||||
const groupsList = userinfo[groupClaimName].map((group) => group.toLowerCase())
|
||||
const adminGroups = (Database.serverSettings.authOpenIDAdminGroups || '').split(',').map((group) => group.trim().toLowerCase()).filter(Boolean)
|
||||
const groupsList = userinfo[groupClaimName].map((group) => group.toLowerCase()).map((group) => (adminGroups.includes(group) ? 'admin' : group))
|
||||
const rolesInOrderOfPriority = ['admin', 'user', 'guest']
|
||||
|
||||
let userType = rolesInOrderOfPriority.find((role) => groupsList.includes(role))
|
||||
const defaultRole = (Database.serverSettings.authOpenIDGroupDefaultRole || '').toLowerCase()
|
||||
let userType = rolesInOrderOfPriority.find((role) => groupsList.includes(role)) || (rolesInOrderOfPriority.includes(defaultRole) ? defaultRole : undefined)
|
||||
if (userType) {
|
||||
if (user.type === 'root') {
|
||||
// Check OpenID Group
|
||||
|
|
@ -210,6 +212,7 @@ class OidcAuthStrategy {
|
|||
if (user.type !== userType) {
|
||||
Logger.info(`[OidcAuth] openid callback: Updating user "${user.username}" type to "${userType}" from "${user.type}"`)
|
||||
user.type = userType
|
||||
user.permissions = Database.userModel.getDefaultPermissionsForUserType(userType)
|
||||
await user.save()
|
||||
}
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -82,6 +82,8 @@ class ServerSettings {
|
|||
this.authOpenIDMobileRedirectURIs = ['audiobookshelf://oauth']
|
||||
this.authOpenIDGroupClaim = ''
|
||||
this.authOpenIDAdvancedPermsClaim = ''
|
||||
this.authOpenIDAdminGroups = ''
|
||||
this.authOpenIDGroupDefaultRole = null
|
||||
this.authOpenIDSubfolderForRedirectURLs = undefined
|
||||
|
||||
if (settings) {
|
||||
|
|
@ -146,6 +148,8 @@ class ServerSettings {
|
|||
this.authOpenIDMobileRedirectURIs = settings.authOpenIDMobileRedirectURIs || ['audiobookshelf://oauth']
|
||||
this.authOpenIDGroupClaim = settings.authOpenIDGroupClaim || ''
|
||||
this.authOpenIDAdvancedPermsClaim = settings.authOpenIDAdvancedPermsClaim || ''
|
||||
this.authOpenIDAdminGroups = settings.authOpenIDAdminGroups || ''
|
||||
this.authOpenIDGroupDefaultRole = settings.authOpenIDGroupDefaultRole || null
|
||||
this.authOpenIDSubfolderForRedirectURLs = settings.authOpenIDSubfolderForRedirectURLs
|
||||
|
||||
if (!Array.isArray(this.authActiveAuthMethods)) {
|
||||
|
|
@ -256,6 +260,8 @@ class ServerSettings {
|
|||
authOpenIDMobileRedirectURIs: this.authOpenIDMobileRedirectURIs, // Do not return to client
|
||||
authOpenIDGroupClaim: this.authOpenIDGroupClaim, // Do not return to client
|
||||
authOpenIDAdvancedPermsClaim: this.authOpenIDAdvancedPermsClaim, // Do not return to client
|
||||
authOpenIDAdminGroups: this.authOpenIDAdminGroups, // Do not return to client
|
||||
authOpenIDGroupDefaultRole: this.authOpenIDGroupDefaultRole, // Do not return to client
|
||||
authOpenIDSubfolderForRedirectURLs: this.authOpenIDSubfolderForRedirectURLs
|
||||
}
|
||||
}
|
||||
|
|
@ -268,6 +274,8 @@ class ServerSettings {
|
|||
delete json.authOpenIDMobileRedirectURIs
|
||||
delete json.authOpenIDGroupClaim
|
||||
delete json.authOpenIDAdvancedPermsClaim
|
||||
delete json.authOpenIDAdminGroups
|
||||
delete json.authOpenIDGroupDefaultRole
|
||||
return json
|
||||
}
|
||||
|
||||
|
|
@ -302,6 +310,8 @@ class ServerSettings {
|
|||
authOpenIDMobileRedirectURIs: this.authOpenIDMobileRedirectURIs, // Do not return to client
|
||||
authOpenIDGroupClaim: this.authOpenIDGroupClaim, // Do not return to client
|
||||
authOpenIDAdvancedPermsClaim: this.authOpenIDAdvancedPermsClaim, // Do not return to client
|
||||
authOpenIDAdminGroups: this.authOpenIDAdminGroups, // Do not return to client
|
||||
authOpenIDGroupDefaultRole: this.authOpenIDGroupDefaultRole, // Do not return to client
|
||||
authOpenIDSubfolderForRedirectURLs: this.authOpenIDSubfolderForRedirectURLs,
|
||||
|
||||
authOpenIDSamplePermissions: User.getSampleAbsPermissions()
|
||||
|
|
|
|||
84
test/server/auth/OidcAuthStrategy.test.js
Normal file
84
test/server/auth/OidcAuthStrategy.test.js
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
const { expect } = require('chai')
|
||||
const sinon = require('sinon')
|
||||
|
||||
const Database = require('../../../server/Database')
|
||||
const Logger = require('../../../server/Logger')
|
||||
const User = require('../../../server/models/User')
|
||||
const OidcAuthStrategy = require('../../../server/auth/OidcAuthStrategy')
|
||||
|
||||
describe('OidcAuthStrategy', () => {
|
||||
let strategy
|
||||
let originalServerSettings
|
||||
let originalUserModel
|
||||
|
||||
beforeEach(() => {
|
||||
strategy = new OidcAuthStrategy()
|
||||
originalServerSettings = Database.serverSettings
|
||||
originalUserModel = Database.userModel
|
||||
Database.userModel = User
|
||||
sinon.stub(Logger, 'info')
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
Database.serverSettings = originalServerSettings
|
||||
Database.userModel = originalUserModel
|
||||
sinon.restore()
|
||||
})
|
||||
|
||||
const fakeUser = (type) => ({ type, username: 'tester', save: sinon.stub().resolves() })
|
||||
|
||||
describe('setUserGroup', () => {
|
||||
it('maps a configured admin group to the admin role', async () => {
|
||||
Database.serverSettings = { authOpenIDGroupClaim: 'groups', authOpenIDAdminGroups: 'syncloud', authOpenIDGroupDefaultRole: null }
|
||||
const user = fakeUser('user')
|
||||
|
||||
await strategy.setUserGroup(user, { groups: ['syncloud'] })
|
||||
|
||||
expect(user.type).to.equal('admin')
|
||||
expect(user.permissions.upload).to.be.true
|
||||
expect(user.save.calledOnce).to.be.true
|
||||
})
|
||||
|
||||
it('still maps built-in role names when admin groups are configured', async () => {
|
||||
Database.serverSettings = { authOpenIDGroupClaim: 'groups', authOpenIDAdminGroups: 'syncloud', authOpenIDGroupDefaultRole: null }
|
||||
const user = fakeUser('guest')
|
||||
|
||||
await strategy.setUserGroup(user, { groups: ['User'] })
|
||||
|
||||
expect(user.type).to.equal('user')
|
||||
})
|
||||
|
||||
it('falls back to the default role when no group matches', async () => {
|
||||
Database.serverSettings = { authOpenIDGroupClaim: 'groups', authOpenIDAdminGroups: '', authOpenIDGroupDefaultRole: 'user' }
|
||||
const user = fakeUser('guest')
|
||||
|
||||
await strategy.setUserGroup(user, { groups: ['some-unrelated-group'] })
|
||||
|
||||
expect(user.type).to.equal('user')
|
||||
})
|
||||
|
||||
it('denies login when no group matches and no default role is set', async () => {
|
||||
Database.serverSettings = { authOpenIDGroupClaim: 'groups', authOpenIDAdminGroups: '', authOpenIDGroupDefaultRole: null }
|
||||
const user = fakeUser('user')
|
||||
|
||||
let error
|
||||
try {
|
||||
await strategy.setUserGroup(user, { groups: ['some-unrelated-group'] })
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error).to.be.an('error')
|
||||
})
|
||||
|
||||
it('does nothing when no group claim is configured', async () => {
|
||||
Database.serverSettings = { authOpenIDGroupClaim: '' }
|
||||
const user = fakeUser('user')
|
||||
|
||||
await strategy.setUserGroup(user, { groups: ['syncloud'] })
|
||||
|
||||
expect(user.type).to.equal('user')
|
||||
expect(user.save.called).to.be.false
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Add a link
Reference in a new issue