...
 
Commits (2)
...@@ -95,7 +95,8 @@ ...@@ -95,7 +95,8 @@
"members": "Members", "members": "Members",
"animators": "Animators", "animators": "Animators",
"admins": "Admins", "admins": "Admins",
"applications": "Applications ID" "applications": "Applications ID",
"nextcloud": "Create groupe in Nextcloud"
} }
}, },
"services": { "services": {
...@@ -186,6 +187,13 @@ ...@@ -186,6 +187,13 @@
"excludeRole": "Excluded Role", "excludeRole": "Excluded Role",
"keycloakId": "Keycloak Id" "keycloakId": "Keycloak Id"
} }
},
"nextcloud": {
"groupExists": "Group already exists in Nextcloud",
"addGroupError": "Error, could not create group in Nextcloud",
"addGroupFolderError": "Error, could not create group folder in Nextcloud",
"removeGroupError": "Warning, could not delete group in Nextcloud",
"removeGroupFolderError": "Warning, could not delete group folder in Nextcloud"
} }
}, },
"components": { "components": {
...@@ -638,7 +646,8 @@ ...@@ -638,7 +646,8 @@
"content": "Long description of the group", "content": "Long description of the group",
"save": "Save", "save": "Save",
"update": "Update", "update": "Update",
"cancel": "Cancel" "cancel": "Cancel",
"nextcloud": "Create group in Nextcloud"
}, },
"SingleGroupPage": { "SingleGroupPage": {
"backToList": "Back to the list", "backToList": "Back to the list",
......
...@@ -95,7 +95,8 @@ ...@@ -95,7 +95,8 @@
"members": "Membres", "members": "Membres",
"animators": "Animateurs", "animators": "Animateurs",
"admins": "Administrateurs", "admins": "Administrateurs",
"applications": "ID de l'application" "applications": "ID de l'application",
"nextcloud": "Créer le groupe dans Nextcloud"
} }
}, },
"services": { "services": {
...@@ -186,6 +187,13 @@ ...@@ -186,6 +187,13 @@
"excludeRole": "Rôle Exclus", "excludeRole": "Rôle Exclus",
"keycloakId": "Identifiant Keycloak" "keycloakId": "Identifiant Keycloak"
} }
},
"nextcloud": {
"groupExists": "Ce groupe existe déjà dans Nextcloud",
"addGroupError": "Erreur lors de la création du groupe dans Nextcloud",
"addGroupFolderError": "Erreur lors de la création du partage dans Nextcloud",
"removeGroupError": "Attention, le groupe n'a pas pu être supprimé dans Nextcloud",
"removeGroupFolderError": "Attention, le dossier n'a pas pu être supprimé dans Nextcloud"
} }
}, },
"components": { "components": {
...@@ -641,7 +649,8 @@ ...@@ -641,7 +649,8 @@
"content": "Longue description du groupe", "content": "Longue description du groupe",
"save": "Sauvegarder", "save": "Sauvegarder",
"update": "Mettre à jour", "update": "Mettre à jour",
"cancel": "Annuler" "cancel": "Annuler",
"nextcloud": "Créer le groupe dans Nextcloud"
}, },
"SingleGroupPage": { "SingleGroupPage": {
"backToList": "Retour à la liste", "backToList": "Retour à la liste",
......
import axios from 'axios'; import axios from 'axios';
import { Meteor } from 'meteor/meteor'; import { Meteor } from 'meteor/meteor';
import AppRoles from './users/users'; import AppRoles from '../users/users';
class KeyCloakClient { class KeyCloakClient {
constructor() { constructor() {
...@@ -29,7 +29,9 @@ class KeyCloakClient { ...@@ -29,7 +29,9 @@ class KeyCloakClient {
const { adminPassword } = Meteor.settings.keycloak; const { adminPassword } = Meteor.settings.keycloak;
return axios.post( return axios.post(
`${this.kcURL}/realms/master/protocol/openid-connect/token`, `${this.kcURL}/realms/master/protocol/openid-connect/token`,
`username=${adminUser}&password=${adminPassword}&grant_type=password&client_id=admin-cli`, `username=${encodeURIComponent(adminUser)}&password=${encodeURIComponent(
adminPassword,
)}&grant_type=password&client_id=admin-cli`,
{ {
headers: { headers: {
'Content-Type': 'application/x-www-form-urlencoded', 'Content-Type': 'application/x-www-form-urlencoded',
...@@ -188,6 +190,22 @@ class KeyCloakClient { ...@@ -188,6 +190,22 @@ class KeyCloakClient {
); );
} }
_addRoleToGroup(groupId, groupName, token) {
return this._getRoleId(groupName, token).then((roleId) =>
axios.post(
`${this.kcURL}/admin/realms/${this.kcRealm}/groups/${groupId}/role-mappings/clients/${this.clientId}`,
[{ name: groupName, id: roleId }],
{
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${token}`,
},
},
),
);
}
_addGroup(groupName, token) { _addGroup(groupName, token) {
return this._addRole(groupName, token).then(() => { return this._addRole(groupName, token).then(() => {
return axios return axios
...@@ -207,19 +225,7 @@ class KeyCloakClient { ...@@ -207,19 +225,7 @@ class KeyCloakClient {
.then(() => this._getGroupId(groupName, token)) .then(() => this._getGroupId(groupName, token))
.then((groupId) => { .then((groupId) => {
console.log(`Keycloak: group ${groupName} added (id ${groupId})`); console.log(`Keycloak: group ${groupName} added (id ${groupId})`);
return this._getRoleId(groupName, token).then((roleId) => { return this._addRoleToGroup(groupId, groupName, token);
return axios.post(
`${this.kcURL}/admin/realms/${this.kcRealm}/groups/${groupId}/role-mappings/clients/${this.clientId}`,
[{ name: groupName, id: roleId }],
{
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${token}`,
},
},
);
});
}); });
}); });
} }
...@@ -254,6 +260,59 @@ class KeyCloakClient { ...@@ -254,6 +260,59 @@ class KeyCloakClient {
}); });
} }
_updateGroup(oldName, groupName) {
this._getToken()
.then((token) => {
// search group id
return this._getGroupId(oldName, token).then((groupId) => {
if (groupId === undefined) {
console.log(`Keycloak: could not find group ${oldName}`);
return null;
}
// delete associated role
return this._removeRole(oldName, token).then(() => {
// update group
return axios
.put(
`${this.kcURL}/admin/realms/${this.kcRealm}/groups/${groupId}`,
{ name: groupName },
{
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${token}`,
},
},
)
.then(() => this._addRole(groupName, token))
.then(() =>
this._addRoleToGroup(groupId, groupName, token).then(() =>
console.log(`Keycloak: changed group name from ${oldName} to ${groupName}`),
),
);
});
});
})
.catch((error) =>
console.log(
`Keycloak: Error updating group ${oldName}`,
error.response && error.response.data ? error.response.data : error,
),
);
}
updateGroupWithRoles(oldName, groupName) {
AppRoles.filter((role) => role !== 'candidate').forEach((role) => {
const oldRole = `${role}_${oldName}`;
const newRole = `${role}_${groupName}`;
this._updateGroup(oldRole, newRole);
});
}
updateGroup(oldName, groupName) {
this._updateGroup(oldName, groupName);
}
_removeGroup(groupName) { _removeGroup(groupName) {
this._getToken() this._getToken()
.then((token) => { .then((token) => {
......
import axios from 'axios';
import { Meteor } from 'meteor/meteor';
const _checkFolderActive = function (response) {
// checks that 'Group Folder' API is responding
if (response.data === undefined || response.data.ocs === undefined) {
console.log(`Nexcloud: ERROR, make sure 'Group Folders' application is active`);
return false;
}
return true;
};
class NextcloudClient {
constructor() {
const ncURL = Meteor.settings.public.nextcloudURL || '';
const ncUser = (Meteor.settings.nextcloud && Meteor.settings.nextcloud.nextcloudUser) || '';
const ncPassword = (Meteor.settings.nextcloud && Meteor.settings.nextcloud.nextcloudPassword) || '';
this.nextURL = `${ncURL}/ocs/v1.php/cloud`;
this.appsURL = `${ncURL}/apps`;
this.basicAuth = Buffer.from(`${ncUser}:${ncPassword}`, 'binary').toString('base64');
}
groupExists(groupName) {
return axios
.get(`${this.nextURL}/groups`, {
params: {
search: groupName,
},
headers: {
Accept: 'application/json',
Authorization: `Basic ${this.basicAuth}`,
'OCS-APIRequest': true,
},
})
.then((response) => {
return response.data.ocs.data.groups.includes(groupName);
})
.catch((error) => {
console.log(`Nextcloud: ERROR getting group ${groupName}`);
console.log(error.response && error.response.data ? error.response.data : error);
return false;
});
}
addGroup(groupName) {
return axios
.post(
`${this.nextURL}/groups`,
{
groupid: groupName,
},
{
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.basicAuth}`,
'OCS-APIRequest': true,
},
},
)
.then((response) => {
const infos = response.data.ocs.meta;
if (infos.status === 'ok') {
console.log(`Nextcloud: group ${groupName} added`);
} else {
console.log(`Nextcloud: ERROR adding group ${groupName} (${infos.statuscode} - ${infos.message})`);
}
return infos.status === 'ok' ? infos.status : infos.message;
})
.catch((error) => {
console.log(`Nextcloud: ERROR adding group ${groupName}`);
console.log(error.response && error.response.data ? error.response.data : error);
return `Nextcloud: ERROR adding group ${groupName}`;
});
}
_addGroupToFolder(groupName, folderName, folderId) {
return axios
.post(
`${this.appsURL}/groupfolders/folders/${folderId}/groups`,
{
group: groupName,
},
{
params: { format: 'json' },
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.basicAuth}`,
'OCS-APIRequest': true,
},
},
)
.then((response) => {
if (response.data.ocs.meta.status === 'ok') {
return axios
.post(
`${this.appsURL}/groupfolders/folders/${folderId}/groups/${groupName}`,
{
// set permissions to : create, read, update, delete (not share)
permissions: 15,
},
{
params: { format: 'json' },
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.basicAuth}`,
'OCS-APIRequest': true,
},
},
)
.then((resp) => resp.data.ocs.meta.status === 'ok');
}
console.log(`Nextcloud: could not assign group ${groupName} to folder ${folderName}`);
return false;
});
}
_addQuotaToFolder(folderId) {
// get quota (in bytes) from settings, or -3 if not set (unlimited)
const quota = Meteor.settings.nextcloud.nextcloudQuota || -3;
return axios
.post(
`${this.appsURL}/groupfolders/folders/${folderId}/quota`,
{
quota,
},
{
params: { format: 'json' },
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.basicAuth}`,
'OCS-APIRequest': true,
},
},
)
.then((response) => {
const infos = response.data.ocs.meta;
if (_checkFolderActive(response) && infos.status === 'ok') {
return true;
}
return false;
});
}
addGroupFolder(groupName, folderName) {
// creates a new group folder and configure access for group users
return axios
.post(
`${this.appsURL}/groupfolders/folders`,
{
mountpoint: folderName,
},
{
params: { format: 'json' },
headers: {
'Content-Type': 'application/json',
Authorization: `Basic ${this.basicAuth}`,
'OCS-APIRequest': true,
},
},
)
.then((response) => {
const infos = response.data.ocs.meta;
if (_checkFolderActive(response) && infos.status === 'ok') {
console.log(`Nextcloud: group folder ${folderName} added`);
return this._addGroupToFolder(groupName, folderName, response.data.ocs.data.id).then((resp) => {
if (resp === true) {
console.log(`Nextcloud: access and permissions set for group folder ${folderName}`);
return this._addQuotaToFolder(response.data.ocs.data.id).then((respQuota) => {
if (respQuota) {
console.log(`Nextcloud: quota set for group folder ${folderName}`);
} else {
console.log(`Nextcloud: ERROR setting quota on group folder ${folderName}`);
}
return respQuota;
});
}
console.log(`Nextcloud: ERROR settings group permissions for group folder ${folderName}`);
return resp;
});
}
console.log(`Nextcloud: ERROR adding group folder ${folderName}`);
return false;
})
.catch((error) => {
console.log(`Nextcloud: ERROR adding group folder ${folderName}`);
console.log(error.response && error.response.data ? error.response.data : error);
return false;
});
}
removeGroupFolder(groupName) {
return axios
.get(`${this.appsURL}/groupfolders/folders`, {
params: { format: 'json' },
headers: {
Accept: 'application/json',
Authorization: `Basic ${this.basicAuth}`,
'OCS-APIRequest': true,
},
})
.then((response) => {
if (_checkFolderActive(response) && response.data.ocs.meta.status === 'ok') {
// find groupFolder ID for groupName
const folders = Object.values(response.data.ocs.data).filter((entry) => {
return entry.mount_point === groupName && Object.keys(entry.groups).includes(groupName);
});
return Promise.all(
folders.map((folder) => {
// check that folder is linked to group
return axios
.delete(`${this.appsURL}/groupfolders/folders/${folder.id}`, {
params: { format: 'json' },
headers: {
Accept: 'application/json',
Authorization: `Basic ${this.basicAuth}`,
'OCS-APIRequest': true,
},
})
.then((resp) => {
const infos = resp.data.ocs.meta;
if (infos.status === 'ok') {
console.log(`Nextcloud: removed group folder ${folder.id} (${folder.mount_point})`);
return true;
}
console.log(`Nextcloud: ERROR deleting group folder ${folder.id} (${infos.message})`);
return false;
});
}),
).then((responses) => !responses.includes(false));
}
return false;
});
}
removeGroup(groupName) {
return axios
.delete(`${this.nextURL}/groups/${groupName}`, {
headers: {
Authorization: `Basic ${this.basicAuth}`,
'OCS-APIRequest': true,
},
})
.then((response) => {
const infos = response.data.ocs.meta;
if (infos.status === 'ok') {
console.log(`Nextcloud: group ${groupName} removed`);
} else {
console.log(`Nextcloud: Error removing group ${groupName} (${infos.message})`);
}
return infos.status === 'ok';
})
.catch((error) => {
console.log(`Nextcloud: ERROR removing group ${groupName}`);
console.log(error.response && error.response.data ? error.response.data : error);
return false;
});
}
}
const nextEnabled = Meteor.settings.public.enableNextcloud === true;
const nextClient = Meteor.isServer && nextEnabled ? new NextcloudClient() : null;
export default nextClient;
...@@ -121,6 +121,11 @@ Groups.schema = new SimpleSchema( ...@@ -121,6 +121,11 @@ Groups.schema = new SimpleSchema(
defaultValue: 0, defaultValue: 0,
label: getLabel('api.groups.labels.numCandidates'), label: getLabel('api.groups.labels.numCandidates'),
}, },
nextcloud: {
type: Boolean,
defaultValue: false,
label: getLabel('api.groups.labels.nextcloud'),
},
}, },
{ tracker: Tracker }, { tracker: Tracker },
); );
...@@ -149,6 +154,7 @@ Groups.publicFields = { ...@@ -149,6 +154,7 @@ Groups.publicFields = {
type: 1, type: 1,
owner: 1, owner: 1,
numCandidates: 1, numCandidates: 1,
nextcloud: 1,
}; };
Groups.allPublicFields = { Groups.allPublicFields = {
content: 1, content: 1,
......
...@@ -9,7 +9,8 @@ import i18n from 'meteor/universe:i18n'; ...@@ -9,7 +9,8 @@ import i18n from 'meteor/universe:i18n';
import { isActive, getLabel } from '../utils'; import { isActive, getLabel } from '../utils';
import Groups from './groups'; import Groups from './groups';
import { addGroup, removeElement } from '../personalspaces/methods'; import { addGroup, removeElement } from '../personalspaces/methods';
import kcClient from '../kcClient'; import kcClient from '../appclients/kcClient';
import nextClient from '../appclients/nextcloud';
export const favGroup = new ValidatedMethod({ export const favGroup = new ValidatedMethod({
name: 'groups.favGroup', name: 'groups.favGroup',
...@@ -22,8 +23,8 @@ export const favGroup = new ValidatedMethod({ ...@@ -22,8 +23,8 @@ export const favGroup = new ValidatedMethod({
throw new Meteor.Error('api.groups.favGroup.notPermitted', i18n.__('api.users.mustBeLoggedIn')); throw new Meteor.Error('api.groups.favGroup.notPermitted', i18n.__('api.users.mustBeLoggedIn'));
} }
// check group existence // check group existence
const service = Groups.findOne(groupId); const group = Groups.findOne(groupId);
if (service === undefined) { if (group === undefined) {
throw new Meteor.Error('api.groups.favGroup.unknownService', i18n.__('api.groups.unknownGroup')); throw new Meteor.Error('api.groups.favGroup.unknownService', i18n.__('api.groups.unknownGroup'));
} }
const user = Meteor.users.findOne(this.userId); const user = Meteor.users.findOne(this.userId);
...@@ -64,6 +65,29 @@ export const unfavGroup = new ValidatedMethod({ ...@@ -64,6 +65,29 @@ export const unfavGroup = new ValidatedMethod({
}, },
}); });
function _createGroup({ name, type, content, description, nextcloud, userId }) {
try {
const groupId = Groups.insert({
name,
type,
content,
description,
owner: userId,
admins: [userId],
active: true,
nextcloud,
});
Roles.addUsersToRoles(userId, 'admin', groupId);
favGroup._execute({ userId }, { groupId });
} catch (error) {
if (error.code === 11000) {
throw new Meteor.Error('api.groups.createGroup.duplicateName', i18n.__('api.groups.groupAlreadyExist'));
} else {
throw error;
}
}
}
export const createGroup = new ValidatedMethod({ export const createGroup = new ValidatedMethod({
name: 'groups.createGroup', name: 'groups.createGroup',
validate: new SimpleSchema({ validate: new SimpleSchema({
...@@ -71,35 +95,36 @@ export const createGroup = new ValidatedMethod({ ...@@ -71,35 +95,36 @@ export const createGroup = new ValidatedMethod({
type: { type: SimpleSchema.Integer, min: 0, label: getLabel('api.groups.labels.type') }, type: { type: SimpleSchema.Integer, min: 0, label: getLabel('api.groups.labels.type') },
description: { type: String, label: getLabel('api.groups.labels.description') }, description: { type: String, label: getLabel('api.groups.labels.description') },
content: { type: String, label: getLabel('api.groups.labels.content') }, content: { type: String, label: getLabel('api.groups.labels.content') },
}).validator(), nextcloud: { type: Boolean, defaultValue: false, label: getLabel('api.groups.labels.nextcloud') },
}).validator({ clean: true }),
run({ name, type, content, description }) { run({ name, type, content, description, nextcloud }) {
if (!isActive(this.userId)) { if (!isActive(this.userId)) {
throw new Meteor.Error('api.groups.createGroup.notLoggedIn', i18n.__('api.users.mustBeLoggedIn')); throw new Meteor.Error('api.groups.createGroup.notLoggedIn', i18n.__('api.users.mustBeLoggedIn'));
} }
try {
const groupId = Groups.insert({
name,
type,
content,
description,
owner: this.userId,
admins: [this.userId],
active: true,
});
Roles.addUsersToRoles(this.userId, 'admin', groupId);
favGroup._execute({ userId: this.userId }, { groupId });
} catch (error) {
if (error.code === 11000) {
throw new Meteor.Error('api.groups.createGroup.duplicateName', i18n.__('api.groups.groupAlreadyExist'));
} else {
throw error;
}
}
if (kcClient) { if (kcClient) {
// create associated groups and roles in keycloak // create associated groups and roles in keycloak
kcClient.addGroup({ name }); kcClient.addGroup({ name });
} }
if (nextcloud && nextClient) {
// create associated group in Nextcloud
return nextClient.addGroup(name).then((response) => {
if (response === 'ok') {
return nextClient.addGroupFolder(name, name).then((res) => {
if (res === false)
throw new Meteor.Error(
'api.groups.createGroup.nextcloudError',
i18n.__('api.nextcloud.addGroupFolderError'),
);
else return _createGroup({ name, type, content, description, nextcloud, userId: this.userId });
});
}
const msg =
response === 'group exists' ? i18n.__('api.nextcloud.groupExists') : i18n.__('api.nextcloud.addGroupError');
throw new Meteor.Error('api.groups.createGroup.nextcloudError', msg);
});
}
return _createGroup({ name, type, content, description, nextcloud, userId: this.userId });
}, },
}); });
...@@ -131,9 +156,44 @@ export const removeGroup = new ValidatedMethod({ ...@@ -131,9 +156,44 @@ export const removeGroup = new ValidatedMethod({
Groups.remove(groupId); Groups.remove(groupId);
// remove from users favorite groups // remove from users favorite groups
Meteor.users.update({ favGroups: { $all: [groupId] } }, { $pull: { favGroups: groupId } }, { multi: true }); Meteor.users.update({ favGroups: { $all: [groupId] } }, { $pull: { favGroups: groupId } }, { multi: true });
if (nextClient && group.nextcloud) {
// remove group from nextcloud if it exists
return nextClient.groupExists(group.name).then((resExists) => {
if (resExists) {
return nextClient.removeGroupFolder(group.name).then((response) => {
if (response)
return nextClient.removeGroup(group.name).then((res) => {
if (res === false)
throw new Meteor.Error(
'api.groups.removeGroup.nextcloudError',
i18n.__('api.nextcloud.removeGroupError'),
);
});
throw new Meteor.Error(
'api.groups.removeGroup.nextcloudError',
i18n.__('api.nextcloud.removeGroupFolderError'),
);
});
}
return null;
});
}
return null;
}, },
}); });
function _updateGroup(groupId, groupData) {
try {
Groups.update({ _id: groupId }, { $set: groupData });
} catch (error) {
if (error.code === 11000) {
throw new Meteor.Error('api.groups.updateGroup.duplicateName', i18n.__('api.groups.groupAlreadyExist'));
} else {
throw error;
}
}
}
export const updateGroup = new ValidatedMethod({ export const updateGroup = new ValidatedMethod({
name: 'groups.updateGroup', name: 'groups.updateGroup',
validate: new SimpleSchema({ validate: new SimpleSchema({
...@@ -156,6 +216,7 @@ export const updateGroup = new ValidatedMethod({ ...@@ -156,6 +216,7 @@ export const updateGroup = new ValidatedMethod({
'data.active': { type: Boolean, optional: true, label: getLabel('api.groups.labels.active') }, 'data.active': { type: Boolean, optional: true, label: getLabel('api.groups.labels.active') },
'data.groupPadId': { type: String, optional: true, label: getLabel('api.groups.labels.groupPadId') }, 'data.groupPadId': { type: String, optional: true, label: getLabel('api.groups.labels.groupPadId') },
'data.digest': { type: String, optional: true, label: getLabel('api.groups.labels.digest') }, 'data.digest': { type: String, optional: true, label: getLabel('api.groups.labels.digest') },
'data.nextcloud': { type: Boolean, optional: true, label: getLabel('api.groups.labels.nextcloud') },
}).validator({ clean: true }), }).validator({ clean: true }),
run({ groupId, data }) { run({ groupId, data }) {
...@@ -178,15 +239,38 @@ export const updateGroup = new ValidatedMethod({ ...@@ -178,15 +239,38 @@ export const updateGroup = new ValidatedMethod({
} else { } else {
groupData = { ...data }; groupData = { ...data };
} }
try { // update group in keycloak if name has changed
Groups.update({ _id: groupId }, { $set: groupData }); if (kcClient && groupData.name && groupData.name !== group.name) {
} catch (error) { kcClient.updateGroup(group.name, groupData.name);
if (error.code === 11000) { }
throw new Meteor.Error('api.groups.updateGroup.duplicateName', i18n.__('api.groups.groupAlreadyExist')); // create nextcloud group if needed
} else { const nextRequired = data.nextcloud === true || (data.nextcloud === undefined && group.nextcloud === true);
throw error; if (nextClient && nextRequired) {
} const groupName = groupData.name || group.name;
return nextClient.groupExists(groupName).then((resExists) => {
if (resExists === false) {
return nextClient.addGroup(groupName).then((response) => {
if (response === 'ok') {
return nextClient.addGroupFolder(groupName, groupName).then((res) => {
if (res === false)
throw new Meteor.Error(
'api.groups.updateGroup.nextcloudError',
i18n.__('api.nextcloud.addGroupFolderError'),
);
_updateGroup(groupId, groupData);
});
}
const msg =
response === 'group exists'
? i18n.__('api.nextcloud.groupExists')
: i18n.__('api.nextcloud.addGroupError');
throw new Meteor.Error('api.groups.updateGroup.nextcloudError', msg);
});
}
return _updateGroup(groupId, groupData);
});
} }
return _updateGroup(groupId, groupData);
}, },
}); });
......
...@@ -64,3 +64,14 @@ Migrations.add({ ...@@ -64,3 +64,14 @@ Migrations.add({
Articles.rawCollection().update({}, { $unset: { visits: true } }, { multi: true }); Articles.rawCollection().update({}, { $unset: { visits: true } }, { multi: true });
}, },
}); });
Migrations.add({
version: 5,
name: 'Add nextcloud setting to groups',
up: () => {
Groups.update({}, { $set: { nextcloud: false } }, { multi: true });
},
down: () => {
Groups.rawCollection().update({}, { $unset: { nextcloud: true } }, { multi: true });
},
});
...@@ -15,7 +15,7 @@ import { structures } from '../structures'; ...@@ -15,7 +15,7 @@ import { structures } from '../structures';
import { favGroup, unfavGroup } from '../../groups/methods'; import { favGroup, unfavGroup } from '../../groups/methods';
import PersonalSpaces from '../../personalspaces/personalspaces'; import PersonalSpaces from '../../personalspaces/personalspaces';
import { createRoleNotification, createRequestNotification } from '../../notifications/server/notifsutils'; import { createRoleNotification, createRequestNotification } from '../../notifications/server/notifsutils';
import kcClient from '../../kcClient'; import kcClient from '../../appclients/kcClient';
// users.findUsers: Returns users using pagination // users.findUsers: Returns users using pagination
// filter: string to search for in username/firstname/lastname/emails (case insensitive search) // filter: string to search for in username/firstname/lastname/emails (case insensitive search)
......
...@@ -19,14 +19,14 @@ Meteor.startup(() => { ...@@ -19,14 +19,14 @@ Meteor.startup(() => {
const rememberMe = window.localStorage.getItem('rememberMe') || 'false'; const rememberMe = window.localStorage.getItem('rememberMe') || 'false';
if (rememberMe === 'false') { if (rememberMe === 'false') {
// warns user when he closes window / reloads page // warns user when he closes window / reloads page
window.onbeforeunload = function (event) { window.onbeforeunload = function onBeforeUnload(event) {
// Cancel the event as stated by the standard. // Cancel the event as stated by the standard.
event.preventDefault(); event.preventDefault();
// Safari // Safari
return ''; return '';
}; };
// disconnect user if 'remember me' is disabled // disconnect user if 'remember me' is disabled
window.onunload = function () { window.onunload = function onUnload() {
Meteor.logout(); Meteor.logout();
window.localStorage.clear('Meteor.loginToken'); window.localStorage.clear('Meteor.loginToken');
window.localStorage.clear('Meteor.loginTokenExpires'); window.localStorage.clear('Meteor.loginTokenExpires');
......
/* eslint-disable react/no-this-in-sfc */ /* eslint-disable react/no-this-in-sfc */
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Meteor } from 'meteor/meteor';
import { withTracker } from 'meteor/react-meteor-data'; import { withTracker } from 'meteor/react-meteor-data';
import i18n from 'meteor/universe:i18n'; import i18n from 'meteor/universe:i18n';
import { import {
...@@ -16,6 +17,9 @@ import { ...@@ -16,6 +17,9 @@ import {
FormControl, FormControl,
Tabs, Tabs,
Tab, Tab,
FormGroup,
FormControlLabel,
Checkbox,
} from '@material-ui/core'; } from '@material-ui/core';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
...@@ -93,6 +97,8 @@ const AdminSingleGroupPage = ({ group, ready, match: { params } }) => { ...@@ -93,6 +97,8 @@ const AdminSingleGroupPage = ({ group, ready, match: { params } }) => {
const [loading, setLoading] = useState(!!params._id); const [loading, setLoading] = useState(!!params._id);
const [tabId, setTabId] = React.useState(0); const [tabId, setTabId] = React.useState(0);
const [content, setContent] = useState(''); const [content, setContent] = useState('');
const [nextcloud, setNextcloud] = useState(false);
const nextEnabled = Meteor.settings.public.enableNextcloud === true;
const history = useHistory(); const history = useHistory();
const classes = useStyles(); const classes = useStyles();
...@@ -104,6 +110,7 @@ const AdminSingleGroupPage = ({ group, ready, match: { params } }) => { ...@@ -104,6 +110,7 @@ const AdminSingleGroupPage = ({ group, ready, match: { params } }) => {
setLoading(false); setLoading(false);
setGroupData(group); setGroupData(group);
setContent(group.content || ''); setContent(group.content || '');
setNextcloud(group.nextcloud);
} }
}, [group]); }, [group]);
...@@ -146,12 +153,14 @@ const AdminSingleGroupPage = ({ group, ready, match: { params } }) => { ...@@ -146,12 +153,14 @@ const AdminSingleGroupPage = ({ group, ready, match: { params } }) => {
data: { data: {
...rest, ...rest,
content, content,
nextcloud,
}, },
}; };
} else { } else {
args = { args = {
...rest, ...rest,
content, content,
nextcloud,
}; };
} }
...@@ -186,7 +195,7 @@ const AdminSingleGroupPage = ({ group, ready, match: { params } }) => { ...@@ -186,7 +195,7 @@ const AdminSingleGroupPage = ({ group, ready, match: { params } }) => {
variant="outlined" variant="outlined"
fullWidth fullWidth
margin="normal" margin="normal"
disabled={!isAdmin && !!params._id} disabled={(!isAdmin || (nextEnabled && group.nextcloud)) && !!params._id}
/> />
<TextField <TextField
onChange={onUpdateField} onChange={onUpdateField}
...@@ -198,6 +207,22 @@ const AdminSingleGroupPage = ({ group, ready, match: { params } }) => { ...@@ -198,6 +207,22 @@ const AdminSingleGroupPage = ({ group, ready, match: { params } }) => {
margin="normal" margin="normal"
disabled disabled
/> />
{nextEnabled ? (
<FormGroup>
<FormControlLabel
control={
<Checkbox
checked={nextcloud}
onChange={() => setNextcloud(!nextcloud)}
name="nextcloud"
color="primary"
disabled={!isAdmin && !!params._id}
/>
}
label={i18n.__('pages.AdminSingleGroupPage.nextcloud')}
/>
</FormGroup>
) : null}
<FormControl> <FormControl>
<InputLabel htmlFor="type" id="type-label"> <InputLabel htmlFor="type" id="type-label">
{i18n.__('pages.AdminSingleGroupPage.type')} {i18n.__('pages.AdminSingleGroupPage.type')}
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
"enableKeycloak": true, "enableKeycloak": true,
"keycloakUrl": "https://auth.appseducation.fr/auth", "keycloakUrl": "https://auth.appseducation.fr/auth",
"keycloakRealm": "apps", "keycloakRealm": "apps",
"enableNextcloud": false,
"nextcloudURL": "",
"minioSSL": false, "minioSSL": false,
"minioPort": null, "minioPort": null,
"minioAccess": "Uhjm4KJ8MV", "minioAccess": "Uhjm4KJ8MV",
...@@ -42,6 +44,11 @@ ...@@ -42,6 +44,11 @@
"adminUser": "", "adminUser": "",
"adminPassword": "" "adminPassword": ""
}, },
"nextcloud": {
"nextcloudUser": "",
"nextcloudPassword": "",
"nextcloudQuota": "1073741824"
},
"private": { "private": {
"fillWithFakeData": true, "fillWithFakeData": true,
"minioSecret": "Pr6SvbtBkutUHmdlN2uYWi5g7oo5EZquMq9k2t00", "minioSecret": "Pr6SvbtBkutUHmdlN2uYWi5g7oo5EZquMq9k2t00",
......