Commit 958828c3 authored by Emmanuel Raviart's avatar Emmanuel Raviart
Browse files

Authentication, authorization & DB: Use MySQL foreign data wrapper for...

Authentication, authorization & DB: Use MySQL foreign data wrapper for authentication, authorization & user infos.
parent 97ba9322
......@@ -41,10 +41,69 @@ CREATE EXTENSION IF NOT EXISTS pg_trgm;
logout # For Debian only
```
As normal user, install dependencies and create database tables:
As normal user, install dependencies:
```bash
npm install
```
### Adding (mostly read-only) Account for Data-Catalogue to Prodedo existing MySQL Database
On Progedo MySQL database server:
```bash
mysql quetelet_commande
CREATE USER 'data_catalogue'@'localhost' IDENTIFIED BY 'data_catalogue';
GRANT SELECT ON diffuseur TO 'data_catalogue'@'localhost';
GRANT SELECT ON discipline TO 'data_catalogue'@'localhost';
GRANT SELECT ON pays TO 'data_catalogue'@'localhost';
GRANT INSERT, SELECT, UPDATE ON profil TO 'data_catalogue'@'localhost';
GRANT SELECT ON role TO 'data_catalogue'@'localhost';
GRANT SELECT ON statut TO 'data_catalogue'@'localhost';
GRANT INSERT, SELECT, UPDATE ON utilisateur TO 'data_catalogue'@'localhost';
GRANT INSERT, SELECT, UPDATE ON utilisateur_option TO 'data_catalogue'@'localhost';
exit
```
#### Using _Debian GNU/Linux_
```bash
apt install postgresql-13-mysql-fdw
su - postgres
psql data_catalogue
```
#### Using _MacOS_
```bash
brew install postgresql-13-mysql-fdw # TODO ?
psql data_catalogue
```
#### For everybody
```sql
CREATE EXTENSION IF NOT EXISTS mysql_fdw;
CREATE SERVER progedo_mysql_server
FOREIGN DATA WRAPPER mysql_fdw
OPTIONS (host '127.0.0.1', port '3306');
GRANT USAGE ON FOREIGN SERVER progedo_mysql_server TO postgres;
CREATE USER MAPPING FOR postgres
SERVER progedo_mysql_server
OPTIONS (username 'data_catalogue', password 'data_catalogue');
GRANT USAGE ON FOREIGN SERVER progedo_mysql_server TO data_catalogue;
CREATE USER MAPPING FOR data_catalogue
SERVER progedo_mysql_server
OPTIONS (username 'data_catalogue', password 'data_catalogue');
exit
```
As normal user, create database tables:
```bash
npm run configure
```
......
......@@ -10,10 +10,15 @@ DB_USER="data_catalogue"
# Change value of DB_PASSWORD!
DB_PASSWORD="data_catalogue"
# DEV_AUTHENTICATION='{"email": "exemple.admin@example.com", "full_name": "Exemple Admin", "preferred_username": "admin", "roles": ["data_catalogue_admin"]}'
# DEV_AUTHENTICATION='{"email": "exemple.diffuseur@example.com", "full_name": "Exemple Diffuseur", "preferred_username": "diffuseur", "roles": ["data_catalogue_diffuseur"]}'
DEV_AUTHENTICATION='{"email": "exemple.support@example.com", "full_name": "Exemple Suport", "preferred_username": "support", "roles": ["data_catalogue_support"]}'
# DEV_AUTHENTICATION='{"email": "exemple.utilisateur@example.com", "full_name": "Exemple Utilisateur", "preferred_username": "utilisateur", "roles": ["data_catalogue_utilisateur"]}'
DEV_AUTHENTICATION='{"email": "john.doe@example.com", "family_name": "Doe", "given_name": "John"}'
# MySQL database configuration
MYSQL_DB_HOST="localhost"
MYSQL_DB_PORT=3306
MYSQL_DB_NAME="quetelet_commande"
MYSQL_DB_USER="data_catalogue"
# Change value of DB_PASSWORD!
MYSQL_DB_PASSWORD="data_catalogue"
# OpenID Connect configuration
# OPENID_CONNECT_CLIENT_ID="OPENID_CONNECT_CLIENT_ID"
......
{
"name": "data-catalogue",
"version": "0.5.0",
"version": "0.7.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"version": "0.5.0",
"version": "0.7.0",
"hasInstallScript": true,
"license": "AGPL-3.0-or-later",
"dependencies": {
......@@ -20,6 +20,7 @@
"node-fetch": "^2.6.1",
"node-stream-zip": "^1.12.0",
"sirv": "^1.0.0",
"slug": "^4.0.4",
"ws": "^7.4.2"
},
"devDependencies": {
......@@ -95,7 +96,6 @@
"rollup-plugin-terser": "^7.0.0",
"sanitize-filename": "^1.6.3",
"sapper": "^0.29.0",
"slug": "^4.0.2",
"sockette": "^2.0.6",
"svelte": "^3.17.3",
"svelte-check": "^1.1.17",
......@@ -7781,8 +7781,7 @@
"node_modules/slug": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/slug/-/slug-4.0.4.tgz",
"integrity": "sha512-GwSsWjX2rcIAtbKWGaxnzASWljWraPQry0RmCnRkji2pEggVjgb5HjXdqN0Gb6JOPg19rsC3hHUYXbOnHXKv2Q==",
"dev": true
"integrity": "sha512-GwSsWjX2rcIAtbKWGaxnzASWljWraPQry0RmCnRkji2pEggVjgb5HjXdqN0Gb6JOPg19rsC3hHUYXbOnHXKv2Q=="
},
"node_modules/sockette": {
"version": "2.0.6",
......@@ -15027,8 +15026,7 @@
"slug": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/slug/-/slug-4.0.4.tgz",
"integrity": "sha512-GwSsWjX2rcIAtbKWGaxnzASWljWraPQry0RmCnRkji2pEggVjgb5HjXdqN0Gb6JOPg19rsC3hHUYXbOnHXKv2Q==",
"dev": true
"integrity": "sha512-GwSsWjX2rcIAtbKWGaxnzASWljWraPQry0RmCnRkji2pEggVjgb5HjXdqN0Gb6JOPg19rsC3hHUYXbOnHXKv2Q=="
},
"sockette": {
"version": "2.0.6",
......
......@@ -6,7 +6,7 @@
"type": "git",
"url": "https://git.nomics.world/progedo/data-catalogue.git"
},
"version": "0.5.0",
"version": "0.7.0",
"author": "DBnomics Team",
"scripts": {
"build": "sapper build --legacy",
......@@ -34,6 +34,7 @@
"node-fetch": "^2.6.1",
"node-stream-zip": "^1.12.0",
"sirv": "^1.0.0",
"slug": "^4.0.4",
"ws": "^7.4.2"
},
"devDependencies": {
......@@ -109,7 +110,6 @@
"rollup-plugin-terser": "^7.0.0",
"sanitize-filename": "^1.6.3",
"sapper": "^0.29.0",
"slug": "^4.0.2",
"sockette": "^2.0.6",
"svelte": "^3.17.3",
"svelte-check": "^1.1.17",
......
......@@ -35,15 +35,17 @@ export function auditConfig(audit: Audit, data: any): [any, any] {
auditHttpUrl,
auditRequire,
)
audit.attribute(
data,
"db",
true,
errors,
remainingKeys,
auditDb,
auditRequire,
)
for (const key of ["db", "mySqlDb"]) {
audit.attribute(
data,
key,
true,
errors,
remainingKeys,
auditDb,
auditRequire,
)
}
audit.attribute(
data,
"devAuthentication",
......
<script lang="ts">
import { stores } from "@sapper/app"
import type { Profil } from "../progedo/data"
const { session } = stores()
$: roles = $session.roles
$: user = $session.user as Profil | undefined
$: user = $session.user
$: role = user?.utilisateur?.role
</script>
{#if user == null}
......@@ -19,15 +21,10 @@
<strong>Access restricted!</strong>
Your credentials don't allow you to access to this page.
</p>
{#if roles.size === 0}
{#if role == null}
<i class="italic">You don't have any role.</i>
{:else}
<p>Your roles:</p>
<ul class="list-disc list-inside">
{#each [...roles] as role}
<li>{role}</li>
{/each}
</ul>
<p>Your role: {role.nom}</p>
{/if}
</div>
{/if}
......@@ -2,6 +2,7 @@
import { goto, stores } from "@sapper/app"
import { validateJsonResponse } from "../auditors/responses"
import { proposedLanguagesAndLabels } from "../locales"
import type { Profil } from "../progedo/data"
import { debugMode, language, localize, shoppingCart } from "../stores"
export let display: string
......@@ -21,9 +22,9 @@
new URL($page.path, $session.baseUrl).toString(),
)}`
// $: roles = $session.roles
$: user = $session.user as Profil | undefined
$: user = $session.user
$: role = user?.utilisateur?.role
async function login() {
open = false
......@@ -32,6 +33,7 @@
async function logout() {
open = false
userMenuOpen = false
await goto(logoutUrl)
}
......@@ -165,23 +167,39 @@
<div class="group inline-block relative">
<button
class="portal mx-2"
value={user.username}
on:click={() => (userMenuOpen = !userMenuOpen)}
>
<span class="mr-1">{user.username}</span>
<span
class="mr-1"
title="{user.prenom} {user.nom} <{user.email}> {role == null
? ''
: `(${role.nom})`} ">{user.prenom}</span
>
</button>
{#if userMenuOpen}
<ul
class="absolute bg-white text-gray-700 pt-1 group-hover:block shadow-sm z-10"
>
<li class="text-sm px-4 py-2">
<a href="profil" value="profil">{_("My Profile")}</a>
<a
href="profil"
on:click={() => (userMenuOpen = false)}
value="profil">{_("My_Profile")}</a
>
</li>
<li class="text-sm px-4 py-2">
<a href="bucket" value="bucket">{_("My Bucket")}</a>
<a
href="cart"
on:click={() => (userMenuOpen = false)}
value="cart">{_("My_Bucket")}</a
>
</li>
<li class="text-sm px-4 py-2">
<a href="commandes" value="commandes">{_("My Requests")}</a>
<a
href="commandes"
on:click={() => (userMenuOpen = false)}
value="commandes">{_("My_Requests")}</a
>
</li>
<li class="text-sm px-4 py-2">
<a href={logoutUrl} on:click|preventDefault={logout}>
......
......@@ -8,8 +8,6 @@
// const { session } = stores()
// $: _ = $localize
// $: roles = $session.roles
</script>
<nav
......
......@@ -12,6 +12,13 @@ const config = {
password: process.env.DB_PASSWORD,
},
devAuthentication: process.env.DEV_AUTHENTICATION,
mySqlDb: {
host: process.env.MYSQL_DB_HOST,
port: process.env.MYSQL_DB_PORT,
database: process.env.MYSQL_DB_NAME,
user: process.env.MYSQL_DB_USER,
password: process.env.MYSQL_DB_PASSWORD,
},
openIdConnect: process.env.OPENID_CONNECT_CLIENT_ID
? {
clientId: process.env.OPENID_CONNECT_CLIENT_ID,
......
......@@ -3,6 +3,7 @@ import dedent from "dedent-js"
import { Language, postgreSqlConfigurationNameByLanguage } from "./data"
import { db, versionNumber } from "./database"
import { configureProgedoForeignDataWrapper } from "./progedo/configure"
export async function configure(): Promise<void> {
await configureDatabase()
......@@ -66,6 +67,8 @@ async function configureDatabase(): Promise<void> {
// Tables
await configureProgedoForeignDataWrapper()
// Table: languages
await db.none(
dedent`
......@@ -192,16 +195,6 @@ async function configureDatabase(): Promise<void> {
`,
)
// Table: users
await db.none(
dedent`
CREATE TABLE IF NOT EXISTS users (
username text NOT NULL PRIMARY KEY,
email text NOT NULL
)
`,
)
// Table: variables
await db.none(
dedent`
......
......@@ -27,7 +27,7 @@ export const db = pgPromise({
user: config.db.user,
})
export let dbSharedConnectionObject: IConnected<{}, IClient> | null = null
export const versionNumber = 2
export const versionNumber = 3
/// Check that database exists and is up to date.
export async function connectDb(): Promise<void> {
......
......@@ -56,6 +56,7 @@ Privacy_policy = Privacy policy
Producer = Producer
producer = producer
Producers = Producers
Profile = Profile
Project = Project
Resources = Resources
Search = Search
......
......@@ -56,6 +56,7 @@ Privacy_policy = Politique de confidentialité
Producer = Producteur
producer = producteur
Producers = Producteurs
Profile = Profil
Project = Série d'études
Resources = Ressources
Search = Rechercher
......
import dedent from "dedent-js"
import config from "../config"
import { db } from "../database"
export async function configureProgedoForeignDataWrapper() {
await db.none(
dedent`
CREATE FOREIGN TABLE IF NOT EXISTS diffuseur (
id bigint NOT NULL,
acronyme character varying(16) NOT NULL,
nom character varying(128) NOT NULL,
adresse text NOT NULL,
liste_distribution_csv text NOT NULL,
default_validator_id bigint,
autovalidation_ignore_other_university boolean DEFAULT false,
remote_file_ftp_host character varying(255) DEFAULT NULL::character varying,
remote_file_ftp_path character varying(255) DEFAULT NULL::character varying,
remote_file_ftp_login character varying(255) DEFAULT NULL::character varying,
remote_file_ftp_password character varying(255) DEFAULT NULL::character varying
)
SERVER progedo_mysql_server
OPTIONS (dbname $<dbname>, table_name 'diffuseur')
`,
{
dbname: config.mySqlDb.database,
},
)
await db.none(
dedent`
CREATE FOREIGN TABLE IF NOT EXISTS discipline (
id bigint NOT NULL,
nom character varying(128) NOT NULL,
ordre bigint DEFAULT '0'::bigint NOT NULL
)
SERVER progedo_mysql_server
OPTIONS (dbname $<dbname>, table_name 'discipline')
`,
{
dbname: config.mySqlDb.database,
},
)
await db.none(
dedent`
CREATE FOREIGN TABLE IF NOT EXISTS pays (
id bigint NOT NULL,
continent_id bigint NOT NULL,
nom character varying(128) NOT NULL,
cog character varying(5) NOT NULL
)
SERVER progedo_mysql_server
OPTIONS (dbname $<dbname>, table_name 'pays')
`,
{
dbname: config.mySqlDb.database,
},
)
await db.none(
dedent`
CREATE FOREIGN TABLE IF NOT EXISTS profil (
id bigint NOT NULL,
utilisateur_id bigint NOT NULL,
complet boolean DEFAULT false NOT NULL,
version bigint NOT NULL,
email character varying(255) NOT NULL,
nom character varying(128) NOT NULL,
prenom character varying(128) NOT NULL,
telephone character varying(32) DEFAULT NULL::character varying,
date_creation timestamp with time zone,
statut_id bigint,
statut_autre character varying(255) DEFAULT NULL::character varying,
discipline_id bigint,
institution_id bigint,
institution_autre character varying(255) DEFAULT NULL::character varying,
entite_type_id bigint,
entite_nom character varying(255) DEFAULT NULL::character varying,
entite_code character varying(255) DEFAULT NULL::character varying,
entite_adresse_rue character varying(255) DEFAULT NULL::character varying,
entite_adresse_cp character varying(16) DEFAULT NULL::character varying,
entite_adresse_ville character varying(255) DEFAULT NULL::character varying,
entite_adresse_pays_id bigint,
entite_url character varying(512) DEFAULT NULL::character varying,
formation character varying(128) DEFAULT NULL::character varying,
resp_1_type_id bigint,
resp_1_nom character varying(128) DEFAULT NULL::character varying,
resp_1_prenom character varying(128) DEFAULT NULL::character varying,
resp_1_email character varying(255) DEFAULT NULL::character varying,
resp_2_type_id bigint,
resp_2_nom character varying(128) DEFAULT NULL::character varying,
resp_2_prenom character varying(128) DEFAULT NULL::character varying,
resp_2_email character varying(255) DEFAULT NULL::character varying
)
SERVER progedo_mysql_server
OPTIONS (dbname $<dbname>, table_name 'profil')
`,
{
dbname: config.mySqlDb.database,
},
)
await db.none(
dedent`
CREATE FOREIGN TABLE IF NOT EXISTS role (
id bigint NOT NULL,
code character varying(32) NOT NULL,
nom character varying(64) NOT NULL
)
SERVER progedo_mysql_server
OPTIONS (dbname $<dbname>, table_name 'role')
`,
{
dbname: config.mySqlDb.database,
},
)
await db.none(
dedent`
CREATE FOREIGN TABLE IF NOT EXISTS statut (
id bigint NOT NULL,
statut_type_id bigint NOT NULL,
nom character varying(64) DEFAULT NULL::character varying,
autre boolean DEFAULT false NOT NULL,
ordre bigint DEFAULT '0'::bigint NOT NULL
)
SERVER progedo_mysql_server
OPTIONS (dbname $<dbname>, table_name 'statut')
`,
{
dbname: config.mySqlDb.database,
},
)
await db.none(
dedent`
CREATE FOREIGN TABLE IF NOT EXISTS utilisateur (
id bigint NOT NULL,
mot_de_passe text NOT NULL,
valide boolean DEFAULT false NOT NULL,
actif boolean DEFAULT false NOT NULL,
date_creation timestamp with time zone,
role_id bigint NOT NULL,
diffuseur_id bigint,
profil_principal_id bigint,
salt character varying(255) NOT NULL
)
SERVER progedo_mysql_server
OPTIONS (dbname $<dbname>, table_name 'utilisateur')
`,
{
dbname: config.mySqlDb.database,
},
)
await db.none(
dedent`
CREATE FOREIGN TABLE IF NOT EXISTS utilisateur_option (
id bigint NOT NULL,
utilisateur_id bigint NOT NULL,
code character varying(255) NOT NULL,
value text
)
SERVER progedo_mysql_server
OPTIONS (dbname $<dbname>, table_name 'utilisateur_option')
`,
{
dbname: config.mySqlDb.database,
},
)
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
import type { ProgedoFollow, ProgedoJsonData, RetrievalError } from "./data"
import { fromProgedoJson } from "./data_consumers"
import { stringifyQuery } from "../urls"
import { validateJsonResponse } from "../auditors/responses"
export type ErrorHandler<T> = (
location: string,
url: string,
response: { status: number },
result: T,
error: any,
) => T
export interface Errorwise {
error?: RetrievalError
}
export function gotoRetrievalError<T extends Errorwise>(goto: any) {
return (
location: string,
url: string,
response: { status: number },
result: T,
error: any,
) => {
return goto(
response.status,
`Error at ${location}, while accessing "${url}": ${JSON.stringify(
error,
null,
2,
)}\n\n${JSON.stringify(result, null, 2)}`,
)
}
}
export function logRetrievalError<T extends Errorwise>(
location: string,
url: string,
response: { status: number },
result: T,
error: any,
): T {
console.error(
response.status,
`Error at ${location}, while retrieving data from ${url}: ${JSON.stringify(
error,
null,
2,
)}\n\n${JSON.stringify(result, null, 2)}`,
)
return {
...result,
error: {
error,
location,
status: response.status,
url,
},
}
}
export function quiet404RetrievalError<T extends Errorwise>(
errorHandler: ErrorHandler<T>,