Commit c82f8d29 authored by Florent Chehab's avatar Florent Chehab
Browse files

opening the beta

parents

Too many changes to show.

To preserve performance only 252 of 252+ files are displayed.
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# Matches multiple files with brace expansion notation
# Set default charset
[*]
charset = utf-8
# Tab indentation (no size specified)
indent_style = tab
.DS_Store
application/cache/*
!application/cache/index.html
!application/cache/.htaccess
application/logs/*
!application/logs/index.html
!application/logs/.htaccess
application/config/cas.php
application/config/config.php
application/config/email.php
application/config/database.php
composer.lock
user_guide_src/build/*
user_guide_src/cilexer/build/*
user_guide_src/cilexer/dist/*
user_guide_src/cilexer/pycilexer.egg-info/*
/vendor/
# IDE Files
#-------------------------
/nbproject/
.idea/*
## Sublime Text cache files
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
*.sublime-workspace
*.sublime-project
RewriteEngine on
RewriteBase /form-UT/
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?/$1 [L,QSA]
Redirect "/user_guide" "/codeigniter_stuff/user_guide"
DROP TABLE IF EXISTS Users CASCADE;
DROP TABLE IF EXISTS Groups CASCADE;
DROP TABLE IF EXISTS UserGroupAssocations CASCADE;
DROP TABLE IF EXISTS UserGroupAssocations CASCADE;
DROP TABLE IF EXISTS Forms CASCADE;
DROP TABLE IF EXISTS GroupFormRelations CASCADE;
DROP TABLE IF EXISTS FormAccess CASCADE;
DROP TABLE IF EXISTS FormSubmissions CASCADE;
DROP TABLE IF EXISTS FormSubmitted CASCADE;
CREATE TABLE Users(
userId SERIAL PRIMARY KEY,
login VARCHAR(255) UNIQUE NOT NULL, --CAS user login
email VARCHAR(255), --Email of the user
type VARCHAR(255), --type of the user given by the CAS system
language VARCHAR(10)
);
CREATE TABLE Groups(
groupId SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
creator INTEGER REFERENCES Users(userId),
active BOOLEAN,
type VARCHAR(50) --user
);
INSERT INTO Groups(title,type) VALUES ('MASTER ADMINS','APP ESSENTIALS');
INSERT INTO Groups(title,type) VALUES ('Everyone','APP ESSENTIALS');
DROP RULE IF EXISTS preventDeleteMasterAdmins on Groups;
CREATE RULE preventDeleteMasterAdmins --PRevent dumb deletion...
AS ON DELETE TO Groups
WHERE (OLD.groupId = 1 or OLD.groupId=2)
DO INSTEAD NOTHING;
CREATE TABLE UserGroupAssocations(
assocId SERIAL PRIMARY KEY,
userId INTEGER REFERENCES Users(userId) NOT NULL,
groupId INTEGER REFERENCES Groups(groupId) NOT NULL,
UNIQUE(userId,groupId)
);
CREATE TABLE Forms(
formId SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
infoJSON JSON,
creator INTEGER REFERENCES Users(userId) NOT NULL,
formJSON JSON,
draft BOOLEAN NOT NULL,
creationDate TIMESTAMP,
lastModificationDate TIMESTAMP,
closeDate TIMESTAMP,
publicResult BOOLEAN NOT NULL,
publicJSONsource BOOLEAN NOT NULL,
anonymous SMALLINT NOT NULL,
personnalInfo SMALLINT NOT NULL,
finalResult TEXT, --csv actually
tmp BOOLEAN NOT NULL,
CHECK(anonymous=0 OR anonymous=1 OR anonymous=2)
);
DROP FUNCTION IF EXISTS updateFormTime() CASCADE;
CREATE FUNCTION updateFormTime()
RETURNS trigger AS'
BEGIN
NEW.lastModificationDate=now();
RETURN NEW;
END' LANGUAGE 'plpgsql';
DROP TRIGGER IF EXISTS formsUpdate on Forms;
CREATE TRIGGER formsUpdate --to make sure forms lastModificationDate is correct
BEFORE UPDATE
ON Forms
FOR EACH ROW
WHEN (
OLD.infoJSON::text IS DISTINCT FROM NEW.infoJSON::text OR
OLD.formJSON::text IS DISTINCT FROM NEW.formJSON::text OR
OLD.title IS DISTINCT FROM NEW.title OR
OLD.draft IS DISTINCT FROM NEW.draft OR
OLD.closeDate IS DISTINCT FROM NEW.closeDate OR
OLD.publicResult IS DISTINCT FROM NEW.publicResult OR
OLD.publicJSONsource IS DISTINCT FROM NEW.publicJSONsource OR
OLD.anonymous IS DISTINCT FROM NEW.anonymous OR
OLD.finalResult IS DISTINCT FROM NEW.finalResult)
EXECUTE PROCEDURE updateFormTime();
CREATE TABLE GroupFormRelations(
relId SERIAL PRIMARY KEY,
groupId INTEGER REFERENCES Groups(groupId) NOT NULL,
formId INTEGER REFERENCES Forms(formId) NOT NULL,
canAdministrate BOOLEAN NOT NULL,
canSeeResults BOOLEAN NOT NULL,
UNIQUE(groupId,formId)
);
CREATE TABLE FormAccess(
accessId SERIAL PRIMARY KEY,
groupId INTEGER REFERENCES Groups(groupId) NOT NULL,
formId INTEGER REFERENCES Forms(formId) NOT NULL,
UNIQUE(groupId,formId)
);
CREATE TABLE FormSubmissions(
submissionId SERIAL PRIMARY KEY,
formId INTEGER REFERENCES Forms(formId) NOT NULL,
uId VARCHAR(255) NOT NULL,
answers JSON NOT NULL,
results JSON NOT NULL,
userInfo JSON NOT NULL,
draft BOOLEAN NOT NULL,
UNIQUE (formId,uId)
);
DROP FUNCTION IF EXISTS updateFormSubmissionStatus() CASCADE;
CREATE FUNCTION updateFormSubmissionStatus()
RETURNS trigger AS'
BEGIN
UPDATE Forms SET tmp=false WHERE formId=OLD.formId;
RETURN NEW;
END' LANGUAGE 'plpgsql';
DROP TRIGGER IF EXISTS FormSubmissionsUpdate on FormSubmissions;
CREATE TRIGGER FormSubmissionsUpdate --to make sure forms lastModificationDate is correct
AFTER UPDATE
ON FormSubmissions
FOR EACH ROW
WHEN (OLD.answers::text IS DISTINCT FROM NEW.answers::text OR
OLD.draft IS DISTINCT FROM NEW.draft)
EXECUTE PROCEDURE updateFormSubmissionStatus();
CREATE TABLE FormSubmitted(
Id SERIAL PRIMARY KEY,
userId INTEGER REFERENCES Users(userId) NOT NULL,
formId INTEGER REFERENCES Forms(formId) NOT NULL,
submissionId INTEGER REFERENCES FormSubmissions(submissionId), -- can be NULL if anonymous
UNIQUE (userId,formId)
);
CREATE UNIQUE INDEX simpleCheck ON FormSubmitted (submissionId)
WHERE submissionId IS NOT NULL;
-- TRIGGERS
DROP FUNCTION IF EXISTS checkGroupExist(varchar) CASCADE;
CREATE FUNCTION checkGroupExist(varchar) RETURNS bigint
AS 'select count(groupId) from groups where groups.title = $1 AND groups.creator is Null;'
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
DROP FUNCTION IF EXISTS findGroupId(varchar) CASCADE;
CREATE FUNCTION findGroupId(varchar) RETURNS integer
AS 'select groupId from groups where groups.title = $1 LIMIT 1'
LANGUAGE SQL
IMMUTABLE
RETURNS NULL ON NULL INPUT;
DROP FUNCTION IF EXISTS createGroupOnUserInsert() CASCADE;
CREATE FUNCTION createGroupOnUserInsert()
RETURNS trigger AS
$BODY$
BEGIN
INSERT INTO UserGroupAssocations(userId,groupId) VALUES(NEW.userId,2);
IF NEW.type IS NULL OR findGroupId(NEW.type)=1 THEN
ELSIF checkGroupExist(NEW.type)=0 THEN
INSERT INTO Groups(title,type) VALUES(NEW.type,'CAS');
INSERT INTO UserGroupAssocations(userId,groupId) VALUES(NEW.userId,findGroupId(NEW.type));
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql';
DROP TRIGGER IF EXISTS userInsert on Users;
CREATE TRIGGER userInsert
AFTER INSERT
ON Users
FOR EACH ROW
EXECUTE PROCEDURE createGroupOnUserInsert();
DROP FUNCTION IF EXISTS updateGroupOnUserUpdate() CASCADE;
CREATE FUNCTION updateGroupOnUserUpdate()
RETURNS trigger AS
$BODY$
BEGIN
IF OLD.type IS NULL THEN
ELSIF findGroupId(OLD.type)!=1 THEN
DELETE FROM UserGroupAssocations WHERE userId=OLD.userId AND groupId=findGroupId(OLD.type);
END IF;
IF NEW.type IS NULL OR findGroupId(NEW.type)=1 THEN
ELSIF checkGroupExist(NEW.type)=0 THEN
INSERT INTO Groups(title,type) VALUES(NEW.type,'CAS');
INSERT INTO UserGroupAssocations(userId,groupId) VALUES(NEW.userId,findGroupId(NEW.type));
ELSE INSERT INTO UserGroupAssocations(userId,groupId) VALUES(NEW.userId,findGroupId(NEW.type));
END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE 'plpgsql';
DROP TRIGGER IF EXISTS userUpdate on Users;
CREATE TRIGGER userUpdate --to make sure groups are up to date
BEFORE UPDATE
ON Users
FOR EACH ROW
EXECUTE PROCEDURE updateGroupOnUserUpdate();
--------------------------------------------------
--------------------------------------------------
-- views for easy managment
DROP VIEW IF EXISTS draftForms;
Create view draftForms AS
select formId, title, creator from forms where draft=true;
DROP VIEW IF EXISTS getInspiredByEveryone;
Create view getInspiredByEveryone AS
select forms.creator, users.login, forms.title, forms.formid from forms, users where users.userid = forms.creator AND forms.draft=false AND publicJSONsource=true ORDER BY forms.title ASC;
DROP VIEW IF EXISTS getInspiredByYourself;
Create view getInspiredByYourself AS
select forms.creator, forms.title, forms.formid from forms where forms.draft=false ORDER BY forms.title ASC;
DROP VIEW IF EXISTS mainGroups;
Create view mainGroups AS
select groups.groupId, groups.title from Groups where ((groups.type='CAS' OR groups.type = 'APP ESSENTIALS') AND groups.groupId != 1);
DROP VIEW IF EXISTS otherAdministrativeGroups;
Create view otherAdministrativeGroups AS
select groups.groupId, groups.title, UserGroupAssocations.userid from Groups, UserGroupAssocations where ((groups.type='SUB-CAS' OR groups.type = 'UVS') AND groups.groupId != 1 AND UserGroupAssocations.groupId = Groups.groupId) ;
DROP VIEW IF EXISTS activeGroups;
Create view activeGroups AS
select * from Groups where groups.active = true AND groups.title != '';
DROP VIEW IF EXISTS unActiveGroups;
Create view unActiveGroups AS
select * from Groups where groups.active = false;
DROP VIEW IF EXISTS yourGroups;
Create view yourGroups AS
select activeGroups.groupId, activeGroups.title, activeGroups.creator, UserGroupAssocations.userid, users.login from activeGroups, UserGroupAssocations, Users where (activeGroups.type='' AND UserGroupAssocations.groupId = activeGroups.groupId AND UserGroupAssocations.userid = Users.userId);
DROP VIEW IF EXISTS groupsAndParticipantsLogin;
Create view groupsAndParticipantsLogin AS
select groups.groupId, users.login, Groups.creator, groups.active, users.userid, groups.title from Groups, UserGroupAssocations, Users where (UserGroupAssocations.groupId = Groups.groupId AND UserGroupAssocations.userid = Users.userId AND Groups.type = '' AND Groups.title !='');
DROP VIEW IF EXISTS groupCreator;
Create view groupCreator AS
select formId, creator from forms;
DROP VIEW IF EXISTS groupCanAdministrate;
Create view groupCanAdministrate AS
select GroupFormRelations.formId, UserGroupAssocations.userid, Groups.groupId, Groups.title AS groupTitle, forms.creator, forms.title AS formTitle from Forms, GroupFormRelations,UserGroupAssocations, Groups WHERE Forms.formid = GroupFormRelations.formid AND GroupFormRelations.canAdministrate=true AND GroupFormRelations.groupId = groups.groupid AND UserGroupAssocations.groupId = groups.groupId;
DROP VIEW IF EXISTS groupCanSeeResults;
Create view groupCanSeeResults AS
select GroupFormRelations.formId, UserGroupAssocations.userid, Groups.groupId, Groups.title AS groupTitle, forms.creator, forms.title AS formTitle from Forms, GroupFormRelations,UserGroupAssocations, Groups WHERE Forms.formid = GroupFormRelations.formid AND (GroupFormRelations.canSeeResults = true OR GroupFormRelations.canAdministrate=true) AND GroupFormRelations.groupId = groups.groupid AND UserGroupAssocations.groupId = groups.groupId;
DROP VIEW IF EXISTS groupCanRights CASCADE;
Create view groupCanRights AS
select GroupFormRelations.formId, UserGroupAssocations.userid, sum(UserGroupAssocations.userid)<0 as creator, sum(GroupFormRelations.canAdministrate::int)>0 AS canAdministrate, sum( (GroupFormRelations.canAdministrate OR GroupFormRelations.canSeeResults)::int)>0 AS canSeeResults from Forms, GroupFormRelations,UserGroupAssocations, Groups WHERE Forms.formid = GroupFormRelations.formid AND (GroupFormRelations.canSeeResults = true OR GroupFormRelations.canAdministrate=true) AND GroupFormRelations.groupId = groups.groupid AND UserGroupAssocations.groupId = groups.groupId GROUP BY GroupFormRelations.formId, UserGroupAssocations.userid;
DROP VIEW IF EXISTS dumbCreatorVue CASCADE;
Create view dumbCreatorVue AS
select Forms.formId, Forms.creator as userid, 1>0 as creator, 1>0 as canAdministrate, 1>0 as canSeeResults from Forms;
DROP View IF EXISTS unionOfRights CASCADE;
Create view unionOfRights AS
select * from groupCanRights UNION select * from dumbCreatorVue;
DROP VIEW IF EXISTS userFormRightsRecap CASCADE;
Create view userFormRightsRecap AS
SELECT userid, formid, sum(creator::int)>0 AS isCreator, sum(canAdministrate::int)>0 AS canAdministrate, sum(canSeeResults::int)>0 AS canSeeResults FROM unionOfRights GROUP BY userid, formid;
DROP VIEW IF EXISTS groupCanAnswerForm;
Create view groupCanAnswerForm AS
select FormAccess.formId, UserGroupAssocations.userid, Groups.groupId, Groups.title from FormAccess, UserGroupAssocations, Groups WHERE FormAccess.groupid = Groups.groupid AND UserGroupAssocations.groupId = groups.groupId;
<mxfile userAgent="Mozilla/5.0 (X11; Fedora; Linux x86_64; rv:54.0) Gecko/20100101 Firefox/54.0" version="7.1.3" editor="www.draw.io" type="device"><diagram name="Page-1" id="1edceaaf-4c90-ac5e-3ef4-ec7475a2e73c">7V1bc6M2FP41nmk7kwwgEPAYO5vtw7bd2exO20cCiq0uRi7I66S/vgIkLpKwcQxetyYvgYOEQOf2naMjPAOL9cv7NNisfiERimeWEb3MwP3MskzoWexfTnktKZbhmSVlmeKIt6oJj/gfxIkGp25xhLJWQ0pITPGmTQxJkqCQtmhBmpJdu9kzidujboIlUgiPYRCr1N9xRFfixRyrvvAzwsuVGNqEfnnlKQi/LlOyTfiAMws8F3/l5XUgbsbfNFsFEdk1SODdDCxSQmh5tH5ZoDifXTFvoh99FQ87A/MVXcfsxGSHxeWHjs5mn87s7VKU0OZwXfeDbgQBjCLfejYsM3BuTD7CtyDe8hEeSLrOlHGzHV7HQYLa4z+ThD7yRvl5EONlwo5D9jwoZYRvKKWY8emOX6Bkw6jhCsfRh+CVbPOnzijjgTibr0iK/2G3DcQY7HJKuchZsNXiMe/JyAajpihjbT6KqTAr0ocgo7xNSOI42GT4qXrgdZAucTInlJI1byTe9AHH8YLEJC0mQEhF+c4PwRrHubIsyDbF7E0t41e0yy82OiWkmC6VR2LO2dyglwaJ8+w9ImtE01fWhF/1PbfswvXzxrVhSdjV0l6J2qop6A7gWsY1bFndvBYTdsAlpa/UAEVqFIGJsSwsGU3J10o/Tf1sCQmK0TPVyM8aR1E+wjzbBCFOlp9zebq/MWvKh6LjPagpn/h05KSU0IAGJf9zZsfBE4o/kgxTTPL7p2Xb+YbghBZT5sxnzn1BSemCJOwlAlwwEjG52qFctvaLhMr/bi08LBHCQvfkvzcA9x0jCmH4ZD5FIEARe1oN82Es5qElBfDvLREXbrJCh+9YA9vbvNQX2dGS/z/qLgDq7vLw26dfbr58ZtfLVswvhSneFPzlA7A3Lsdoj/uUyhSloSTjjFm0LeOFyWnIlypHwZaS8h1aFrND3kt72RZ2+3iJO9riOLbRtjimKSgNkbM9jcgJMTxF5oCjzDWKmMfnp0wVV2RJkiB+V1PnhRdHUWXr1zjkxw0GsSlJX//g9OLkz/zk1hGe5i6HI7U9Kmi5KxDeoemNrOIekdSFURod2Fmz+V+I0ldOyEWBkeq3+UAKDneYxhbPf0bxN5SLCr/Q8FRG8ZcP/YJp8aq3rsNP/+wEDgJqMFkK+ZRzK5/P/F6ZSVEcUPytjcV07OddP+ZK0ZA1w2nLmiObrfKheLdaiti8B6+NZlzZOgdyHa89kM3N6ENHByb0zt4e7KB8iP79rfarMXFaIqq8WqEg1RT30xnFLH/JcqMwAbnLAXLQlIAchJZiVU3P0wA5YwBX7kyw7QTYBnpzuxukaXk7AGcBmBzmkA6zt7+UnKPGgZ7qL4/2ciKaEDYG2Md6OanHAS8HPQkrQluCgB3+u4+/BNKthvOXIoSWUh+M8rh9WuMsY6ZlyoNclPvUyIfnKDa2IxEyQFhie5MDPcGBVhp3iWkOR03UNj0oip/I7njnyR2KOC5dp6X4zggHa5JEn1c4kVyoovkX7kJVYe/U+6an7EyJjR18mpYSFAqTcsDlHOuX2UhynOsfFUxacpxcxbMH/OwbnKNYOGlYup8U/biQ9FcfsRzC+VhQwjmOqabEdNZpiIyYsx/gn2aepASRIZun7wzteznQ4USiZZe4ypU24Ly26mjgb0n2wfP2A39gygbF3N/BAceOoJisAyO4UB4BHhjBkzpAb49RfYslVJMm75lybW6ey2DhU8HTfHFhihQuJ1KAvuTVPdfVQElXNdbQHcBYq6Fl9yIT826JZuGpTT7oeUsbKdvEo+OMlsPlIYWthhQ5abfCFD0yev5MuzTIOxJ2v+e4cAArFrGgRBN6tDHBnPnGRZ5vYejAWrBzsz4fOv6oVPm0+EMjNJY9gNC4GqGZgsvxmOto0JuOuUMEl96I6K0dXF5aXnYc8BYF2aqYn94RprDJYweUvhzlCYw0dDypDrQva/sG5CPCnYZBMm9vVaBzZWGg4UhhoOtqKiNGCgOhWnxz9XG5L62paqHeWPyY8iRqkl4Al2o5CWiS9GMxRC2DUItor4sh0JNiIUdTyjUaP/Yjn1MWprWLtdUqdTOvVa1aXwU26gWH/kuJLYZ12gLsH6zbkn20v69u6w3YyFWNyuBirRTtNeTamuS6v1yPU8d4tBS7toQbgblXiiHc3+F0gN+R2pyqHi4ql2nKtdieYyr+23Q1hWX+EOX/U1rqpL0eQ+QctcwdgLeuGuFP0YucVtGF92OhZVfVraFhxUypROmEFZX9nWAF0KBl5yJghaksgPJou19ViSOQbL1+2q948y2g2Z7MjRybu3Iy0TijtVHx37XzQ0kmanfajsUPddn42nNXji/Xlgvccw5+qND32vXDkmv9XVGYdg5+qOXX184PYMmxoavGhmPxw7PHRqvq0nbXdiT3Ota93wxWv1d9tetIudu++FJd75aBblXvOPTCuvy1Dcj3Xg6Vd/NU3BWmKKBolmslDNa5fbnoLw6cybp5cgYUAkuxbnAs66aisWv3Nr4ja8YZVxI9FY1dOzr2eDV1Qz/OF614KhrL90/ehSHKphWEoVcQFKHQiM6epKbsO31PAxN1xY/uHo/fW1J8RVKmcmi9PRq7HPqAGJU6fXI5NBigHNpX1yYUvk/rTgMyF2pc+Ujl0L7+q4PFvntKWXA4eY8L8h7KR2uA1XPTPXQGkBVrsgPD2gH/Yvbc+/pvVhVFKHdZRsJiZ90EJi/KHNimnHI07X6eAw6AJX11DXHCkheJJf2Orx99h611/vThs/My94xb63ztXtsyh8oV3hDZ1f96sugkuw0MeSlVg/jHSqaKZfVBN0D+/5d7WtscG3VaSplWObvKx+g02cXmqlCXFp/6QVd5UdIFfvseA63UKANB35ekcm/tk4xkXPlrc8PVPvlj7AC+WgWYtdZ+nTcrQJmZHFwBTANIkuWP9VEpdST7mPI/TX/Z3A+oA/4IOlDvl7Gu7xtGmm3wfVSgWSzQlQ04dd1ftq3AN0bRACh/lQtA4wgFgJZ/C+s/OdPlgFuzcVnsLh1eN0xDzXejCOdPusLZ7J018+Yzz1kVQP3/ViRwEq71fKlIwNZsj/FHwrWmofk1DHf+wzZD+S8nWYv8K1E4+vErep259wqnslWwQYX+U9QR9zfdPeeP3ckRiW1tBkERP86r3zw6nNT4Lhy1pQ3Lledo7okRhYctlh6fcGan9a83lYpb/0gWePcv</diagram></mxfile>
\ No newline at end of file
# Application architecture
This is a short introduction to the architecture used for this application.
## Simplified schema
Here is a simplified schema of the application structure (mainly concerning the database) :
![UML of the application](../data/images/schema.png)
## Anonymous
So users can be anonymous when they answer a form.
The logic behind it is quite simple : because a user should be able to answer a form only once, **we need to store the data about the fact that he/she has answered a form**. The *anonymous* characteristic will take place when creating a link between knowing that a user has answered a form and what is exactly his/her form submission. Therefore, when answereing as an *anonymous user*, the `formSubmissionId` **is not stored** in the `formSubitted` table ! To enable editing of anonymous form submissions, the `uid` of the form submission is given to the user, so that he/she is the only one that can access it again.
- For this to work safely, a **secure** client/server connection should be used.
*About the cool feature of PGP encryption, the encryption is done entirely on the server, only using PHP.*
Conclusion : in the *anonymous case* **It is possible to know if a user has answered a form**. If there has only been one answer to a form the user is only *virtually* anonymous. Otherwise, the user is *technically* anonymous !
## Talk about security
**Making the app as secure as possible has been a big challenge.**
- Besides tradionnal SQL checks to see if a user can do what he/she is about to do, the security relies heavily on the `session` system by setting temporary *checkpoints* forcing the user to have passed the previous *checkoint(s)* or preventing the user from going back to a previous *checkpoint*.
- None of the apps interface (**ajax included**) should *talk* to an unauthorized person. So, for example, throwing a random ajax request will get you nowhere.
- Concerning ajax :
- the user has to be logged-in for any interactions to take place and there systematic checks before sending data back to the user.
- A token system is used so the system cannot answer unlimited *requests*.
## Form valiation
This comes right after security, because the app relies on exactly the same form system as the one the user can use. **So it must be robust...**
- There is of course a client side validation of the form-submissions.
- There is also a server side validation of the form-submissions (**using only data on the server**). This validation will take into account all the constraint available to the user and the *coherence* of their answers (was this answer possible).
**There is also a server side validation for the form created by the users. To make sure only authorized fields are used... and that they are well used.**
This validation enables an *expert mode* for form creation : you can create form element (or entire form) 100% through JSON. *This should be usefull when dealing with a lot of options in a select for example.*
## A small description of the `Answer` controller
Because the structure of this controller might be a little bit complicated to understand, here is a summury :
- To answer a form, the function `form` is used as a switch : if the user has already answered the form he/she is redirected to the `getAccess` function directly. If he/she hasn't answered the form yet, he/she is redirected to the `init` function.
- The `init` function is there to get the user approval on what info is associatted to his/her form submission.
- On success the user is redirected to the `getAccess` function with some cached data so that `getAccess` will redirect the user directly to `edit` function.
- (Otherwise) the `getAccess` function will check if the form submission for this form (and this user) was anonymous, if not we have access to the `submissionId` directly in the database. Otherwise, we have to retreive it.
- So if the user was anonymous, he/she is redirected to the function `anonymousAccess` where he/she is able to type the `uid` he/she was given. If the `uid` is valid (for the `formid`), the original submissionId is retreived from the database, and he/she goes back to the `getAccess` function and then directly to the `edit` function.
# Database : description
## Introduction
For this project, a postgres SQL databse has been use.
The full SQL script can be found [here](./data/code/crea_base.sql).
## Comments on the model
Explanation on the tables :
- `Users`
For this table their is nothing crazy. A `userId` with serial, a `login`, an `email`, a `type` (deducted from the CAS) and a `language` (this attribute is not yet used). `Email` and `type` can be `NULL` to handle group *populating* easily (we accept all logins and create a user for every login that isn't in the db ; this will be done until we can access to the *demeter* infos)
- `Groups`
Nothing crazy either. If `active` attribute is set to `false`, access deducted from this group won't be granted.
Two groups are automaticly created, one containing every user, the other for the Admins, if someday we need such a group.
`preventDeleteMasterAdmins` is here to prevent delation of those groups.
- `UserGroupAssocations`
Table for storing the association between a user and a group.
- `Forms`
Table for holding all the data of the forms : `id`, `title`, `infoJson` (not yet in use, maybe if one day there is translation), the `creator`, `formJson` (the form architecture), is the form a `draft`, the `creationDate`, the `lastModificationDate` (not really used), the `closeDate`, are the results public (`publicResult` - not yet used), is the form architecture public (`publicJSONsource`), what kind of information on the user we store (`anonymous` and `personnalInfo`), the `finalResult` (not yet used), `tmp` if the user hasn't changed the title (those form will be deleted on login/logout for db cleaning).
**Comments on `anonymous` and `personnalInfo`**
For `anonymous` :
- if set to `2` : those who answer will be anonymous,
- if set to `1` : those who answer can choose to be anonymous or not,
- if set to `0` : those who answer can't be anonymous.
For `personnalInfo` :
- if set to `2` : the CAS info of those who answer will be anonimized,
- if set to `1` : those who answer can choose to share their CAS info or not.
- if set to `0` : those who answer have to share their CAS info.
- `GroupFormRelations`
Table holding data on what kind of access is granted to a group for a form. Only `canSeeResults` is used in the app currently : `canAdministrate` is not.
- `FormAccess`
A quite similar table but this one is dedecated to storing the info on the groups that can answer a form.
- `FormSubmissions`
A `uid` is attached to each FormSubmission, this string will be used for identifying anonymous user (enabling them to edit the submissions). `results` is a JSON string containing the results as determined by the js saveFormLib. `answers` is a JSON string containing the serialization of the formSubmission for easy repopulation of the form. `userInfo` store the info related to the user info and CAS groups. Those info are stored on creation of the a submission for homogenous form-submission handling (anonymous or not).
- `FormSubmitted`
A table to know what forms have a user answered : if the user is not anoymous we also store the uid of the form submission.
I am a little bit lazy to comment all the views. But one is really nice : `userFormRightsRecap`. It gives a recap of all the rights a user have *per-form*.
# Description of how the (important) files are organized for this project
This description follows the structure of the repo.
## `application`
This is the folder containing all the Codeigniter and backend related stuff.
- `assets` : this is the folder for all *outside* PHP libraries
- `config` : this folder contains all the Codeigniter/libraries realted config files. __All the `.expample` files have to be changed depending on your installation !__
- `controllers` : this folder contains all the Codeigniter controllers, they are the main entry from the web.
- `core` : this folder contains *overloaded* Codeigniter PHP class : mainly for easy handling of restrective access, Ajax and SQL queries.
- `libraries` : this folder contains all the PHP libraries developped for this project and few others copied from the web (`CAS` and a bit of `PGPlib`).
- `models` : this folder contains every database related functions.
- `views` : this folder contains all the views used for this project, they are "template ready". If you want to create a new template, duplicate the default folder and change the template name in the `layouts.php` library.
## `assets`
This folder holds the assets for the frontend:
- `css` : main css files for the site.
- `js` : all the js libs developped for this project.
- `lang` : some language files for `node_modules` translation.
## `Codeigniter_stuff`
Well some stuff related to Codeigniter.
## `Documentation`
This folder contains all the documentaiton related files.
## `node_modules`
This is the folder storing the **NUMEROUS** js libs tacken from the open-source world for building this awesome project.
# Install tutorial
1. Copy the whole repo on your server (PHP ≥ 7.0 is needed).
- Setup the database and run `crea_database.sql` file.
- Configure the config files of Codeigniter (/application/config). This concerns the `.example` files.
- Mess up with `.htaccess` files and *stackoverflow* for making it works.
# Element used in the form-UT project
## Core backend elements
### Main elements
- `CodeIgniter` v3.1.5
- https://codeigniter.com/
- MIT License
- Reason for this choice : really simple PHP framework to use.
- `postgreSQL` v9.6
- https://www.postgresql.org/
- PostgreSQL License
- Reason for this choice : ...
### Securtity related
- `phpCAS` v1.3.5
- https://wiki.jasig.org/display/CASC/phpCAS
- Apache License 2.0
- Reason for this choice : essential library for **CAS** authentication.
- `codeIgniter cas library` (11th commit)
- https://github.com/eliasdorneles/code-igniter-cas-library
- MIT License
- Reason for this choice : easy implementation for making `phpCAS` working with codeIngniter.
- *Modifications* : Modification of file `Cas.php`.
From :
```PHP
phpCAS::client(CAS_VERSION_2_0, $cas_url['host'],
$cas_url['port'], $cas_url['path']);
```
To :
```PHP
if(isset($_SESSION))
phpCAS::client(CAS_VERSION_2_0, $cas_url['host'],
$cas_url['port'], $cas_url['path'],false);
else
phpCAS::client(CAS_VERSION_2_0, $cas_url['host'],
$cas_url['port'], $cas_url['path']);