From d291a0e0b79c099e3864df17f24517c4cc119cdb Mon Sep 17 00:00:00 2001
From: anatole <anatole.romon@polytechnique.edu>
Date: Mon, 5 Mar 2018 12:48:24 +0100
Subject: [PATCH] docu + travail sur les meta-groupes

---
 .../20180305111321_metaGroup_member_table.js  | 13 +++
 db/seeds/01_create_groups.js                  |  8 ++
 db/seeds/05_metagroup_membership.js           | 23 +++++
 src/graphql/connectors/connectors.js          | 96 ++++++++++++++++++-
 src/graphql/resolvers.js                      | 27 ++++--
 src/graphql/typeDefs.js                       |  3 +-
 6 files changed, 161 insertions(+), 9 deletions(-)
 create mode 100644 db/migrations/20180305111321_metaGroup_member_table.js
 create mode 100644 db/seeds/05_metagroup_membership.js

diff --git a/db/migrations/20180305111321_metaGroup_member_table.js b/db/migrations/20180305111321_metaGroup_member_table.js
new file mode 100644
index 0000000..ab7268d
--- /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 ee9c1ce..fb2d494 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 0000000..cd15875
--- /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/src/graphql/connectors/connectors.js b/src/graphql/connectors/connectors.js
index fdf5bcf..6b431f7 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);
@@ -199,6 +249,7 @@ export async function getAllVisibleMetaGroups (user){
  * @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){
     let all_simple_groups = await getAllVisibleSimpleGroups(user);
@@ -211,6 +262,7 @@ export async function getAllVisibleGroups(user){
  * @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);
@@ -221,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();
@@ -243,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')
@@ -276,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) ){
@@ -292,6 +349,7 @@ 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')
@@ -307,6 +365,7 @@ 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')
@@ -323,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')
@@ -362,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.
@@ -374,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 2c04e50..d7b681c 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) => {
@@ -104,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);
diff --git a/src/graphql/typeDefs.js b/src/graphql/typeDefs.js
index e9e9069..f226961 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
         
     }
@@ -124,6 +124,7 @@ const subQueries = `
 
     type MemberQuery{
         isMember: Boolean
+        allMembers : [Group]
     }
 
     type ViewerQuery{
-- 
GitLab