diff --git a/.eslintrc.json b/.eslintrc.json index ed55f719b7e472b089b05b9ae0c2b6eefe4e735f..6bc9af7e5870290ce9b10f5a7677cbd40d9db172 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -7,6 +7,7 @@ "parserOptions": { "sourceType": "module" }, + "parser": "babel-eslint", "rules": { "no-unused-vars": 0, "indent": ["error",4], diff --git a/db/migrations/20180305111321_metaGroup_member_table.js b/db/migrations/20180305111321_metaGroup_member_table.js new file mode 100644 index 0000000000000000000000000000000000000000..ab7268d3ee913ab518f8be69170f7c9006893411 --- /dev/null +++ b/db/migrations/20180305111321_metaGroup_member_table.js @@ -0,0 +1,13 @@ + +exports.up = function(knex, Promise) { + return knex.schema.createTable('meta_group_membership', function (table){ + table.timestamp(true, true); + table.string('member_uid').notNullable(); + table.string('union_uid').notNullable(); + table.enum('status', ['admin', 'speaker', 'basic']).notNullable(); + }); +}; + +exports.down = function(knex, Promise) { + return knex.schema.dropTable('meta_group_membership'); +}; diff --git a/db/seeds/01_create_groups.js b/db/seeds/01_create_groups.js index ee9c1ce9463b9c3d697978667f06c15da9e955b1..fb2d49459c027fc55830236aced3132281aeb598 100644 --- a/db/seeds/01_create_groups.js +++ b/db/seeds/01_create_groups.js @@ -21,6 +21,14 @@ exports.seed = function(knex, Promise) { school: 'polytechnique', parentuid: 'kes', type : 'simple' + },{ + name: 'Bôbar', + uid: 'bob', + description : "Viens. On est bien", + website: 'bôbar.binet.fr', + school: 'polytechnique', + parentuid: 'kes', + type : 'simple' },{ name: 'Kès', uid: 'kes', diff --git a/db/seeds/05_metagroup_membership.js b/db/seeds/05_metagroup_membership.js new file mode 100644 index 0000000000000000000000000000000000000000..cd1587503a14bf8f86f50d8f3964f34113bae412 --- /dev/null +++ b/db/seeds/05_metagroup_membership.js @@ -0,0 +1,23 @@ + +exports.seed = async function(knex, Promise) { + // Deletes ALL existing entries + await knex('meta_groups').del(); + await knex('meta_group_membership').insert([ + { + member_uid : "br", + union_uid : "federez", + status : "admin" + }, + { + member_uid : "data", + union_uid : "federez", + status : "admin" + }, + { + member_uid : "bob", + union_uid : "bsckbl", + status : "admin" + } + ]); + return; +}; diff --git a/package-lock.json b/package-lock.json index 9f6530332ce026f63a160fc68c6b313652085a24..b802c1f4ee2898b0b22e4c9fddd4c487a1a4308a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,120 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.40.tgz", + "integrity": "sha512-eVXQSbu/RimU6OKcK2/gDJVTFcxXJI4sHbIqw2mhwMZeQ2as/8AhS9DGkEDoHMBBNJZ5B0US63lF56x+KDcxiA==", + "requires": { + "@babel/highlight": "7.0.0-beta.40" + } + }, + "@babel/generator": { + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.0.0-beta.40.tgz", + "integrity": "sha512-c91BQcXyTq/5aFV4afgOionxZS1dxWt8OghEx5Q52SKssdGRFSiMKnk9tGkev1pYULPJBqjSDZU2Pcuc58ffZw==", + "requires": { + "@babel/types": "7.0.0-beta.40", + "jsesc": "2.5.1", + "lodash": "4.17.5", + "source-map": "0.5.7", + "trim-right": "1.0.1" + } + }, + "@babel/helper-function-name": { + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.0.0-beta.40.tgz", + "integrity": "sha512-cK9BVLtOfisSISTTHXKGvBc2OBh65tjEk4PgXhsSnnH0i8RP2v+5RCxoSlh2y/i+l2fxQqKqv++Qo5RMiwmRCA==", + "requires": { + "@babel/helper-get-function-arity": "7.0.0-beta.40", + "@babel/template": "7.0.0-beta.40", + "@babel/types": "7.0.0-beta.40" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0-beta.40.tgz", + "integrity": "sha512-MwquaPznI4cUoZEgHC/XGkddOXtqKqD4DvZDOyJK2LR9Qi6TbMbAhc6IaFoRX7CRTFCmtGeu8gdXW2dBotBBTA==", + "requires": { + "@babel/types": "7.0.0-beta.40" + } + }, + "@babel/highlight": { + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.40.tgz", + "integrity": "sha512-mOhhTrzieV6VO7odgzFGFapiwRK0ei8RZRhfzHhb6cpX3QM8XXuCLXWjN8qBB7JReDdUR80V3LFfFrGUYevhNg==", + "requires": { + "chalk": "2.3.1", + "esutils": "2.0.2", + "js-tokens": "3.0.2" + } + }, + "@babel/template": { + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.0.0-beta.40.tgz", + "integrity": "sha512-RlQiVB7eL7fxsKN6JvnCCwEwEL28CBYalXSgWWULuFlEHjtMoXBqQanSie3bNyhrANJx67sb+Sd/vuGivoMwLQ==", + "requires": { + "@babel/code-frame": "7.0.0-beta.40", + "@babel/types": "7.0.0-beta.40", + "babylon": "7.0.0-beta.40", + "lodash": "4.17.5" + }, + "dependencies": { + "babylon": { + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.40.tgz", + "integrity": "sha512-AVxF2EcxvGD5hhOuLTOLAXBb0VhwWpEX0HyHdAI2zU+AAP4qEwtQj8voz1JR3uclGai0rfcE+dCTHnNMOnimFg==" + } + } + }, + "@babel/traverse": { + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.0.0-beta.40.tgz", + "integrity": "sha512-h96SQorjvdSuxQ6hHFIuAa3oxnad1TA5bU1Zz88+XqzwmM5QM0/k2D+heXGGy/76gT5ajl7xYLKGiPA/KTyVhQ==", + "requires": { + "@babel/code-frame": "7.0.0-beta.40", + "@babel/generator": "7.0.0-beta.40", + "@babel/helper-function-name": "7.0.0-beta.40", + "@babel/types": "7.0.0-beta.40", + "babylon": "7.0.0-beta.40", + "debug": "3.1.0", + "globals": "11.3.0", + "invariant": "2.2.3", + "lodash": "4.17.5" + }, + "dependencies": { + "babylon": { + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.40.tgz", + "integrity": "sha512-AVxF2EcxvGD5hhOuLTOLAXBb0VhwWpEX0HyHdAI2zU+AAP4qEwtQj8voz1JR3uclGai0rfcE+dCTHnNMOnimFg==" + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "@babel/types": { + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.0.0-beta.40.tgz", + "integrity": "sha512-uXCGCzTgMZxcSUzutCPtZmXbVC+cvENgS2e0tRuhn+Y1hZnMb8IHP0Trq7Q2MB/eFmG5pKrAeTIUfQIe5kA4Tg==", + "requires": { + "esutils": "2.0.2", + "lodash": "4.17.5", + "to-fast-properties": "2.0.0" + }, + "dependencies": { + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=" + } + } + }, "@f/animate": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@f/animate/-/animate-1.0.1.tgz", @@ -503,6 +617,26 @@ } } }, + "babel-eslint": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-8.2.2.tgz", + "integrity": "sha512-Qt2lz2egBxNYWqN9JIO2z4NOOf8i4b5JS6CFoYrOZZTDssueiV1jH/jsefyg+86SeNY3rB361/mi3kE1WK2WYQ==", + "requires": { + "@babel/code-frame": "7.0.0-beta.40", + "@babel/traverse": "7.0.0-beta.40", + "@babel/types": "7.0.0-beta.40", + "babylon": "7.0.0-beta.40", + "eslint-scope": "3.7.1", + "eslint-visitor-keys": "1.0.0" + }, + "dependencies": { + "babylon": { + "version": "7.0.0-beta.40", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-7.0.0-beta.40.tgz", + "integrity": "sha512-AVxF2EcxvGD5hhOuLTOLAXBb0VhwWpEX0HyHdAI2zU+AAP4qEwtQj8voz1JR3uclGai0rfcE+dCTHnNMOnimFg==" + } + } + }, "babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", @@ -873,7 +1007,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", - "dev": true, "requires": { "ansi-styles": "3.2.0", "escape-string-regexp": "1.0.5", @@ -1825,7 +1958,6 @@ "version": "3.7.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", - "dev": true, "requires": { "esrecurse": "4.2.0", "estraverse": "4.2.0" @@ -1834,8 +1966,7 @@ "eslint-visitor-keys": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", - "dev": true + "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==" }, "espree": { "version": "3.5.3", @@ -3367,8 +3498,7 @@ "globals": { "version": "11.3.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.3.0.tgz", - "integrity": "sha512-kkpcKNlmQan9Z5ZmgqKH/SMbSmjxQ7QjyNqfXVc8VJcoBV2UEg+sxQD15GQofGRh2hfpwUb70VC31DR7Rq5Hdw==", - "dev": true + "integrity": "sha512-kkpcKNlmQan9Z5ZmgqKH/SMbSmjxQ7QjyNqfXVc8VJcoBV2UEg+sxQD15GQofGRh2hfpwUb70VC31DR7Rq5Hdw==" }, "globby": { "version": "5.0.0", @@ -3523,8 +3653,7 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-value": { "version": "1.0.0", @@ -4088,6 +4217,11 @@ "underscore": "1.8.3" } }, + "jsesc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.1.tgz", + "integrity": "sha1-5CGiqOINawgZ3yiQj3glJrlt0f4=" + }, "json-loader": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", @@ -6629,7 +6763,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", "integrity": "sha512-F39vS48la4YvTZUPVeTqsjsFNrvcMwrV3RLZINsmHo+7djCvuUzSIeXOnZ5hmjef4bajL1dNccN+tg5XAliO5Q==", - "dev": true, "requires": { "has-flag": "3.0.0" } @@ -6860,6 +6993,11 @@ "nopt": "1.0.10" } }, + "trim-right": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", + "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=" + }, "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", diff --git a/package.json b/package.json index 3fb5e2449494c39592716faed3543c5ace252152..03650a7b435f0fcad1fe75e84fb914b6cfe58350 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "Backend of sigma, the new Frankiz", "main": "index.js", "dependencies": { + "babel-eslint": "^8.2.2", "body-parser": "^1.18.2", "colors": "^1.1.2", "connect-ensure-login": "^0.1.1", diff --git a/src/graphql/connectors/connectors.js b/src/graphql/connectors/connectors.js index 0aea999c8b86c9b16380d9ecc67a98c28dbe84b5..6b431f7322b76a167affb16fd27e738b756b80aa 100644 --- a/src/graphql/connectors/connectors.js +++ b/src/graphql/connectors/connectors.js @@ -10,6 +10,51 @@ import { exportAllDeclaration } from 'babel-types'; export { renseignerSurUtilisateur, repliquerTOLdesIds, listerMembres }; +/* +Le tag @rights et la gestion des authorisations + +Le système GraphQL est pensé comme l'interface par laquelle les utilisateurs +intéragissent avec sigma, les graphismes en moins. +Pour cette raison, et pour des questions de sécurité, il faut partir du principe que +l'utilisateir peut rédiger n'importe quelle requête, et que c'est au niveau des resolvers +que la sécurité ce déroule. C'est dans cette optique que j'utilise le tag @rights + +Commençons par un rappel sur le fonctionnement des droits. +Chaque utilisateur a un certain niveau de droit sur chaque groupe. Ce niveau de droit indique +ce qu'il a le droit de savoir et de faire. Chaque niveau est inclu dans les niveaus supérieur. +Les différends niveaux sont : +none - aucun droit +viewer : l'utilisateur a visibilité sur le groupe. Il sait que le groupe existe, et a accès à un certain nombre d'infos. +member : l'utilisateur est membre du groupe +speaker : l'utilisateur peut parler au nom du groupe. Il a le droit de publier des annonces et d'organiser des évènements +admin : l'utilisateur a tous les droits sur le groupe + +Certaines fonctions de connectors effectuent des vérifications d'authorisations avant +de renvoyer une réponse, d'autres non. Pour être sur qu'on ne renvoie jamais de réponse +sans avoir au préalable éffectué les bonnes vérifications, chaque fonction possède dans sa +description un attribut droit, qui décrit les droits que fournit cette fonction. + +La valeur de @rights peut être : +super - la fonction n'effectue aucune vérification, et renvoie le resultat demandé +admin( groupUID ) - la fonction ne fait que ce qu'un admin du groupe indiqué aurait le droit de faire +speaker( groupUID ), member( groupUID ), veiwer( groupUID ) - même chose +user - la fonction ne fait que ce que l'utiliateur a le droit de faire (vérifications via l'argument user) + +La procédure a suivre est la suivante : quand une fonction possède un certain niveau de droit, +elle ne peut appeler une fonction possédant un niveau de droit plus large que si +1 ) on a au préalable vérifié que l'utilisateur possédait effectivement ces droits. +ou +2 ) on s'est assuré que l'opération effectuée par cet appel particulier de la fonction était dans les droits +de l'utilisateur + +Les resolvers de base de mutation et query ont des droits user. + +Les fonctions qui ne modifient pas la BDD et ne renvoient pas de données sur la BDD n'ont pas de rights. +*/ + + + + /** * @summary Génère une promise. * @function @@ -37,6 +82,7 @@ const quickPromise = (val) => { * @arg {Object} groupUID - L'id du groupe dont on veut connaître le type. * @return {Promise(String)} Un string représentant le type du groupe. * Peut être "SimpleGroup" ou "MetaGroup". Renvoie `Undefined` si le groupe n'existe pas + * @rights super */ export const getGroupType = (user, groupUID) => { return knex('simple_groups').select('uid').where('uid', groupUID).then( sg_res => { @@ -60,6 +106,7 @@ export const getGroupType = (user, groupUID) => { * @arg {String} uid - L'uid du groupe dont on veut les administrateurs. * @return {Promise} Retour de requête knex. Promise qui renvera une liste * de tous les utilisateurs ayant droit d'admin sur le groupe + * rights member(groupUID) */ export const getUsersWithAdminRights = (user, groupUID) => { return getGroupType(user, groupUID).then( groupType => { @@ -98,6 +145,7 @@ export const getUsersWithAdminRights = (user, groupUID) => { * @arg {Object} user - Objet contenant un attribut `uid` de type `string`. * User représente l'utilisateur qui a effectué la requête. * @return {Promise} Retour de requête knex. Liste de tous les groupes que l'utilisateur a le droit de voire. + * rights user */ export const hasAdminRights = (user, groupUID) => { if(user.uid == "anatole.romon") @@ -114,6 +162,7 @@ export const hasAdminRights = (user, groupUID) => { * @arg {Object} user - Objet contenant un attribut `uid` de type `string`. * User représente l'utilisateur qui a effectué la requête. * @return {Promise(Callback)} callback contruisant une requête knex pour une table de tous les id visibles. + * @rights user */ export const getVisibleGroupCallback = (user) => { return listerGroupes(user, user.uid).then(group_ids => { @@ -166,6 +215,7 @@ function getGroupTableName(wantedType){ * @arg {String} uid - Identifiant du groupe voulu. * @arg {String} type - Type de groupe voulu. `"simple"`, `"meta"` ou `"all"`. * @return {Promise(group)} Retour de requête knex. Le groupe demandé, si l'utilisateur a le droit de la voire. + * @rights user */ export async function getGroupIfVisible(user, groupUID, type="all"){ let group_table_name = getGroupTableName(type); @@ -174,34 +224,45 @@ export async function getGroupIfVisible(user, groupUID, type="all"){ .from(group_table_name).innerJoin('visible_groups', function (){ this.on('visible_groups.uid', '=', group_table_name + '.uid'); }).where(group_table_name + '.uid', groupUID).then(res => res[0]); -}; +} export const getSimpleGroupIfVisible = (user, groupUID) => getGroupIfVisible(user, groupUID, "simple"); export const getMetaGroupIfVisible = (user, groupUID) => getGroupIfVisible(user, groupUID, "meta"); +export async function getAllVisibleSimpleGroups (user){ + let visible_groups = await getVisibleGroupCallback(user); + return knex.with('visible_groups', visible_groups).select().from("simple_groups").innerJoin('visible_groups', function (){ + this.on('visible_groups.uid', '=', 'simple_groups.uid'); + }); +}; + +export async function getAllVisibleMetaGroups (user){ + let visible_groups = await getVisibleGroupCallback(user); + return knex.with('visible_groups', visible_groups).select().from("meta_groups").innerJoin('visible_groups', function (){ + this.on('visible_groups.uid', '=', 'meta_groups.uid'); + }); +}; + /** * @summary Renvoie tous les groupes visibles par l'utilisateur user * @desc Cette fonction effectue une requête knex. Elle gère l'arête de parenté. * @arg {Object} user - Représente l'utilisateur qui a effectué la requête. * @arg {String} wantedType - Type de groupe voulu : `"simple"`, `"meta"` ou `"all"`. * @return {Promise} Retour de requête knex. Liste de tous les groupes que l'utilisateur a le droit de voire. + * @rights user */ -export async function getAllVisibleGroups(user, wantedType="all"){ - let group_table_name = getGroupTableName(wantedType); - let visible_groups = await getVisibleGroupCallback(user); - return knex.with('visible_groups', visible_groups).select().from(group_table_name).innerJoin('visible_groups', function (){ - this.on('visible_groups.uid', '=', group_table_name + '.uid'); - }); -}; - -export const getAllVisibleSimpleGroups = (user) => getAllVisibleGroups(user,"simple"); -export const getAllVisibleMetaGroups = (user) => getAllVisibleGroups(user,"meta"); +export async function getAllVisibleGroups(user){ + let all_simple_groups = await getAllVisibleSimpleGroups(user); + let all_meta_groups = await getAllVisibleMetaGroups(user); + return all_simple_groups.concat(all_meta_groups); +} /** * @summary Teste si un utilisateur est membre d'un groupe * @arg {Object} user - Représente l'utilisateur qui a effectué la requête. * @arg {Object} groupUID - L'id du groupe dont on veu savoir si l'utilisateur est membre. * @return {Promise(Boolean)} Boolean indiquant si l'utilisateur est membre du groupe. + * @rights user */ export const isMember = (user, groupUID) => { return listerGroupes(user, user.uid).then(group_ids => group_ids && group_ids.indexOf(groupUID) != -1); @@ -212,6 +273,9 @@ export const isMember = (user, groupUID) => { * @desc RASifie le string initialUID si necessaire (ramené à de l'ASCCI sans espace), puis si l'uid est deja pris rajoute un n a la fin et reteste * @arg {String} uid - L'uid du groupe dont on veut les administrateurs. * @return {Promise} Retour de requête knex. Promise qui renvera une liste de tous les utilisateurs ayant droit d'admin sur le groupe + * @rights user + * remarque : n'importe qui peut tester si un groupe existe en demandant a créer un groupe avec ce nom la et en regardant si + * son UID a été modifié. Je ne vois pas comment contourner ce problème, c'est donc une faille permanente de sigma. */ export function getAvailablegroupUID(initialUID){ let rasUID = initialUID.replace(' ', '_').replace(/\W/g, '').toLowerCase(); @@ -222,7 +286,7 @@ export function getAvailablegroupUID(initialUID){ return (getAvailablegroupUID(rasUID + 'n')); } }); -}; +} /** * @summary Créé un groupe si les arguments sont tous valides @@ -234,6 +298,7 @@ export function getAvailablegroupUID(initialUID){ * @arg {Object} user - L'utilisateur qui effectue la requête. * @arg {Object} args - Les arguments envoyés à la mutation. Cf le schéma GraphQL * @return {Promise} Retour de requête knex. Le groupe qui vient d'être créé. En cas d'echec, renvoie une erreur. + * @rights admin (args.parentuid) */ export async function createSubgroup(user, args){ if (typeof args.parentuid != 'string') @@ -257,7 +322,7 @@ export async function createSubgroup(user, args){ }); return getGroupIfVisible(user, rasUID); -}; +} /** * @summary Créé un groupe si les arguments sont tous valides et l'utilisateur est authorisé @@ -267,6 +332,7 @@ export async function createSubgroup(user, args){ * @arg {Object} user - L'utilisateur qui effectue la requête. * @arg {Object} args - Les arguments envoyés à la mutation. Cf le schéma GraphQL * @return {Promise} Retour de requête knex. Le groupe qui vient d'être créé. En cas d'echec, renvoie une erreur. + * @rights user */ export async function createGroupIfLegal(user, args){ if( await hasAdminRights(user, args.parentuid) ){ @@ -274,7 +340,7 @@ export async function createGroupIfLegal(user, args){ }else{ throw "illegal request : you must have admin rights over a group to create a subgroup of that group"; } -}; +} /** * @summary Renvoie toues les requêtes de type UserJoinGroup @@ -283,11 +349,12 @@ export async function createGroupIfLegal(user, args){ * @arg {Object} user - L'utilisateur qui effectue la requête. * @arg {String} args - L'identifiant du groupe qui reçoit la requête. * @return {Promise(Object)} Retour de requête knex. Toutes les requêtes destinées au groupe. + * @rights admin(recipientUID) */ export function getUserJoinGroupRequests(user, recipientUID){ return knex.select('id', 'useruid', 'message').from('user_join_group') .where('recipient', recipientUID); -}; +} /** * @summary Renvoie toues les requêtes de type GroupJoinEvent @@ -298,11 +365,12 @@ export function getUserJoinGroupRequests(user, recipientUID){ * @arg {Object} user - L'utilisateur qui effectue la requête. * @arg {String} args - L'identifiant du groupe qui reçoit la requête. * @return {Promise(Object)} Retour de requête knex. Toutes les requêtes destinées au groupe. + * @rights speaker(recipientUID) */ export function getGroupJoinEventRequests(user, recipientUID){ return knex.select('id', 'senderuid', 'eventuid', 'message').from('group_join_event') .where('recipient', recipientUID); -}; +} /** @@ -314,6 +382,7 @@ export function getGroupJoinEventRequests(user, recipientUID){ * @arg {Object} user - L'utilisateur qui effectue la requête. * @arg {String} args - L'identifiant du groupe qui reçoit la requête. * @return {Promise(Object)} Retour de requête knex. Toutes les requêtes destinées au groupe. + * @rights speaker(recipientUID) */ export const getYourGroupHostEventRequests = (user, recipientUID) => { return knex.select('id', 'senderuid', 'eventuid', 'message').from('your_group_host_event') @@ -353,10 +422,11 @@ export const getEvent = (user, eventID) => { }; /** - * @function Renvoie simplement un groupe en fonction de son identifiant. + * @summary Renvoie simplement un groupe en fonction de son identifiant. * @param {Object} user - Utilisateur effectuant la requête. * @param {String} groupUID - Identifiant unique du groupe. * @author manifold + * @rights super */ export const getGroup = (user, groupUID) => { // Une sélection sur une table renvoie un tableau. @@ -365,14 +435,47 @@ export const getGroup = (user, groupUID) => { return knex.select().from('groups').where('uid',groupUID).then(results => results [0]); }; +/** + * @summary Renvoie simplement un groupe simple en fonction de son identifiant. + * @param {Object} user - Utilisateur effectuant la requête. + * @param {String} groupUID - Identifiant unique du groupe. + * @author manifold + * @rights super + */ export const getSimpleGroup = (user, groupUID) => { return knex.select().from('simple_groups').where('uid',groupUID).then(results => results [0]); }; +/** + * @summary Renvoie simplement un meta groupe en fonction de son identifiant. + * @param {Object} user - Utilisateur effectuant la requête. + * @param {String} groupUID - Identifiant unique du groupe. + * @author manifold + * @rights super + */ export const getMetaGroup = (user, groupUID) => { return knex.select().from('meta_groups').where('uid',groupUID).then(results => results [0]); }; export const getMetaGroupAdminMembers = (user, metaGroupUID) => { return quickPromise([]); +}; + +/** + * @function getMetaGroupMembers + * @summary Renvoie tous les membres d'un meta-groupe. + * @param {Object} user - Utilisateur effectuant la requête. + * @param {String} metaGroupUID - Identifiant unique du groupe. + * @return {Promise(callback)} a callback to build a query for the members of a group + * It doesn't need to be a promise, but I figure having all of my functions return promises is + * easier than keeping track of which functions do and do not return promises. + * @author akka vodol + * @rights member(metaGroupUID) + */ +export async function getMetaGroupMembersCallback(user, metaGroupUID){ + return function(query_builder){ + return query_builder.distinct().select().from('groups') + .innerJoin('meta_group_membership', 'groups.uid', 'meta_group_membership.member_uid') + .where('meta_group_membership.union_uid', '=', metaGroupUID); + }; }; \ No newline at end of file diff --git a/src/graphql/resolvers.js b/src/graphql/resolvers.js index 24c05c153eccf64fa35833b5466292eb07aff507..d7b681cd619ad33549c7016c9bfdff526486346d 100644 --- a/src/graphql/resolvers.js +++ b/src/graphql/resolvers.js @@ -16,13 +16,15 @@ import { connect } from 'http2'; export const resolvers = { Query: { - asAdmin: (obj, args, context) => { - return connectors.hasAdminRights(context.user, args.groupUID).then(res => { - if(res) - return {groupUID : args.groupUID}; - else - throw "You do not have admin rights over this group"; - }); + asAdmin: async function (obj, args, context){ + if(await connectors.hasAdminRights(context.user, args.groupUID)) + return {groupUID : args.groupUID}; + else + throw "You do not have admin rights over this group"; + }, + + asMember: function (obj, args, context){ + return {groupUID : args.groupUID}; }, accessGroups: (obj, args, context) => { @@ -42,6 +44,10 @@ export const resolvers = { allGroups: (obj, args, context) => { return connectors.getAllVisibleGroups(context.user); }, + allSimpleGroups: (obj, args, context) => { + return connectors.getAllVisibleSimpleGroups(context.user); + }, + group: (obj, args, context) => { return connectors.getGroupIfVisible(context.user, args.uid); }, @@ -100,6 +106,17 @@ export const resolvers = { } }, + MemberQuery: { + isMember: (obj, args, context) => { + return true; + }, + + allMembers: async function (obj, args, context){ + let cb = await connectors.getMetaGroupMembersCallback(context.user, obj.groupUID); + return cb(knex); + } + }, + AllRequests: { userJoinGroup : (obj, args, context) => { return connectors.getUserJoinGroupRequests(context.user, obj.groupUID); @@ -243,8 +260,6 @@ export const resolvers = { SimpleGroup: { members: (obj, args, context) => { - console.log("Current group is",obj.uid); - console.log("\tMembers of the group are:",obj); return connectors.listerMembres(context.user,obj.uid); } }, diff --git a/src/graphql/typeDefs.js b/src/graphql/typeDefs.js index b77e9ea4bd531601dfba749d4aed7d7056145596..f226961c163cf0bf7a2c96ee935dd1f9c69d45de 100644 --- a/src/graphql/typeDefs.js +++ b/src/graphql/typeDefs.js @@ -7,7 +7,7 @@ const RootTypes = ` asAdmin(groupUID: ID): AdminQuery asSpeaker(groupUID: ID): AdminQuery - asMember(groupUID: ID): AdminQuery + asMember(groupUID: ID): MemberQuery asViewer(groupUID: ID): AdminQuery } @@ -51,7 +51,7 @@ const subMutations = ` type SpeakerMutation{ postEvent(name: String, date: String): Event - answerRequest(request: ID, accept : Boolean): Request + answerEventRequest(request: ID, accept : Boolean): Request } type MemberMutation { @@ -72,6 +72,8 @@ const subQueries = ` """ type GroupQuery{ allGroups: [Group] + allSimpleGroups: [SimpleGroup] + group(uid: ID) : Group simpleGroup(uid : ID) : SimpleGroup metaGroup(uid : ID) : MetaGroup @@ -122,6 +124,7 @@ const subQueries = ` type MemberQuery{ isMember: Boolean + allMembers : [Group] } type ViewerQuery{