Skip to content

Commit

Permalink
Map Discord roles to NextCloud groups.
Browse files Browse the repository at this point in the history
Fixes #390
  • Loading branch information
kousu committed Aug 13, 2023
1 parent d935209 commit eb0ea74
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 2 deletions.
2 changes: 1 addition & 1 deletion js/settings.js

Large diffs are not rendered by default.

22 changes: 21 additions & 1 deletion lib/Service/ProviderService.php
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class ProviderService
'id' => 'appid',
'secret' => 'secret',
],
'group_mapping' => 'groupMapping',
],
self::TYPE_OPENID => [
'openid_identifier' => 'url',
Expand Down Expand Up @@ -232,7 +233,7 @@ public function handleDefault($provider)
{
$config = [];
$scopes = [
'discord' => 'identify email guilds',
'discord' => 'identify email guilds guilds.members.read',
];
$providers = json_decode($this->config->getAppValue($this->appName, 'oauth_providers'), true) ?: [];
if (is_array($providers) && in_array($provider, array_keys($providers))) {
Expand Down Expand Up @@ -413,6 +414,24 @@ private function auth($class, array $config, $provider, $providerType = null)
throw new LoginException($this->l->t('Login is available only to members of the following Discord guilds: %s', $config['guilds']));
};
$checkGuilds();

// read Discord roles into NextCloud groups
$profile->data['groups'] = [];
if(!empty($allowedGuilds)) {
foreach($userGuilds as $guild) {
if (!in_array($guild->id ?? null, $allowedGuilds)) {
// Only read groups from the explicitly declared guilds.
// It doesn't make sense to try to map in random, unknown groups from arbitrary guilds.
// and without this, a user in many guilds will trip a HTTP 429 rate limit from the Discord API.
continue;
}
# https://discord.com/developers/docs/resources/guild#get-guild-member
$guild_data = $adapter->apiRequest('users/@me/guilds/' . $guild->id . '/member' );
$profile->data['groups'] = array_merge($profile->data['groups'], $guild_data->roles ?? []);
// TODO: /member returns roles as their ID; to get their name requires an extra API call
// (and perhaps extra permissions?)
}
}
}

if (!empty($config['logout_url'])) {
Expand All @@ -422,6 +441,7 @@ private function auth($class, array $config, $provider, $providerType = null)
}

$profile->data['default_group'] = $config['default_group'];
$profile->data['group_mapping'] = $config['group_mapping'];

if ($provider === 'telegram') {
$provider = 'tg'; //For backward compatibility
Expand Down
6 changes: 6 additions & 0 deletions lib/Settings/AdminSettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ public function getForm()
'secret' => '',
];
}

if($provider == 'discord') {
// HACK: make sure the Discord provider understands groups
// 'hasGroupMapping' is more like 'supports group mappings'
$providers[$provider]['hasGroupMapping'] = true;
}
}
$customProviders = json_decode($this->config->getAppValue($this->appName, 'custom_providers'), true);

Expand Down
36 changes: 36 additions & 0 deletions src/components/Settings.vue
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,20 @@
<input type="text" :name="'providers['+name+'][guilds]'" v-model="provider.guilds"/>
</label>
</template>
<template v-if="provider.hasGroupMapping">
<button class="group-mapping-add" type="button" @click="provider.groupMapping.push({foreign: '', local: ''})">
{{ t(appName, 'Add group mapping') }}
</button>
<div v-for="(mapping, mappingIdx) in provider.groupMapping" :key="mapping">
<input type="text" class="foreign-group" v-model="mapping.foreign" />
<select class="local-group" :name="mapping.foreign ? 'providers['+name+'][groupMapping]['+mapping.foreign+']' : ''">
<option v-for="group in groups" :key="group" :value="group" :selected="mapping.local === group">
{{ group }}
</option>
</select>
<span class="group-mapping-remove" @click="provider.groupMapping.splice(mappingIdx, 1)">x</span>
</div>
</template>
</div>
<br/>

Expand Down Expand Up @@ -148,6 +162,10 @@ export default {
data.custom_providers = {}
}
// In the 'custom' providers, those that can have multiple instances of the same protocol, rewrite groupMapping
// from { foreign0: local0, foreign1, local1, ... }
// to [ { 'foreign': foreign0, 'local': local0 }, { 'foreign': foreign1, 'local': local1 }, ... ]
// This is necessary for Vue to use groupMapping as a read-write data model.
for (var provType in providerTypes) {
if (!data.custom_providers[provType]) {
data.custom_providers[provType] = []
Expand All @@ -165,6 +183,24 @@ export default {
}
}
}
// For the fixed providers, with only a singleton instance of the login server, rewrite groupMapping
// from { foreign0: local0, foreign1, local1, ... }
// to [ { 'foreign': foreign0, 'local': local0 }, { 'foreign': foreign1, 'local': local1 }, ... ]
// This is necessary for Vue to use groupMapping as a read-write data model.
for (var provType in data.providers) {
if (data.providers[provType].hasGroupMapping) {
var groupMappingArr = []
var groupMapping = data.providers[provType].groupMapping
if (groupMapping) {
for (var foreignGroup in groupMapping) {
groupMappingArr.push({foreign: foreignGroup, local: groupMapping[foreignGroup]})
}
}
data.providers[provType].groupMapping = groupMappingArr
}
}
data.appName = appName
return data
},
Expand Down

0 comments on commit eb0ea74

Please sign in to comment.