Commit 36fc0c42 authored by Florian Azizian's avatar Florian Azizian
Browse files

Merge branch 'develop' into 'master'

Develop

See merge request maarch/MaarchParapheur!63
parents 3f4bf368 8e7720d3
......@@ -67,6 +67,11 @@
<!-- <mode>
<id>eidas</id>
<color>#00FF00</color>
</mode>
<mode>
<id>external</id>
<color>#FF0000</color>
<issuer></issuer>
</mode> -->
</signatureModes>
</ROOT>
......@@ -511,6 +511,16 @@
"mergedVariablesMsg": "Use tag",
"mergedVariablesMsg2": "to display the generated security code.",
"mergedVariablesMsgEmail": "You can use the different tags below to enrich your email",
"updateOtp": "Update external user"
"updateOtp": "Update external user",
"smsCodeTagMandatory": "The <b> code </b> tag is required to send security code by SMS",
"emailLinkTagMandatory": "The <b> access link </b> tag is required for email notification",
"manage_customization": "Customization",
"manage_customizationDesc": "Customize the message on the login screen",
"loginMessage": "Message on the login page:",
"customization": "Customization",
"noConnector": "The associated connector does not exist",
"otpVisaUser": "The user will be notified by <b> email </b> at the time of his turn in the workflow.",
"messageSaved": "Message saved",
"manage_customizationAdmin": "Customize the login page"
}
}
\ No newline at end of file
......@@ -517,6 +517,12 @@
"loginMessage": "Message à l'écran de connexion :",
"customization": "Personnalisation",
"noConnector": "Le connecteur associé n'existe pas",
"otpVisaUser": "L'utilisateur sera notifié par <b>courriel</b> au moment de son tour dans le circuit."
"otpVisaUser": "L'utilisateur sera notifié par <b>courriel</b> au moment de son tour dans le circuit.",
"messageSaved": "Message enregistré",
"manage_customizationAdmin": "Personnaliser la page de connexion",
"internalUserOtpMsg": "<b>{{user}}</b> sera converti en utilisateur externe, vous ne pourrez plus choisir les autres modes de signature.",
"externalUser": "Role externe",
"visa_yousignUser": "Viseur (Yousign)",
"sign_yousignUser": "Signataire (Yousign)"
}
}
......@@ -84,6 +84,9 @@ $app->delete('/connectors/{id}', \ExternalSignatoryBook\controllers\ExternalSign
$app->put('/connectors/{id}', \ExternalSignatoryBook\controllers\ExternalSignatoryBookController::class . ':update');
$app->get('/connectors/{id}/currentVisa', \ExternalSignatoryBook\controllers\ExternalSignatoryBookController::class . ':getCurrentVisa');
//Customization
$app->put('/customization', \Configuration\controllers\ConfigurationController::class . ':updateCustomization');
//Documents
$app->post('/documents', \Document\controllers\DocumentController::class . ':create');
$app->get('/documents', \Document\controllers\DocumentController::class . ':get');
......
......@@ -217,6 +217,7 @@ CREATE TABLE users
"password" character varying(255) NOT NULL,
firstname character varying(128) NOT NULL,
lastname character varying(128) NOT NULL,
phone character varying(128),
picture text,
"isRest" boolean DEFAULT FALSE NOT NULL,
preferences jsonb NOT NULL DEFAULT '{"lang" : "fr", "writingMode" : "direct", "writingSize" : 1, "writingColor" : "#000000", "notifications" : true}',
......
......@@ -359,6 +359,42 @@ class ConfigurationController
return $response->withJson(['connection' => true, 'informations' => 'success']);
}
public function updateCustomization(Request $request, Response $response, array $args)
{
if (!PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_customization'])) {
return $response->withStatus(403)->withJson(['errors' => 'Privilege forbidden']);
}
$body = $request->getParsedBody();
if (empty($body)) {
return $response->withStatus(400)->withJson(['errors' => 'Body is not set or empty']);
} elseif (!Validator::stringType()->notEmpty()->validate($body['loginMessage'])) {
return $response->withStatus(400)->withJson(['errors' => 'Body loginMessage is empty or not a string']);
}
$configuration = ConfigurationModel::getByIdentifier(['identifier' => 'customization']);
if (empty($configuration[0])) {
return $response->withStatus(400)->withJson(['errors' => 'Configuration does not exist']);
}
$configuration = $configuration[0];
$data = json_encode(['loginMessage' => $body['loginMessage']]);
ConfigurationModel::update(['set' => ['value' => $data], 'where' => ['id = ?'], 'data' => [$configuration['id']]]);
HistoryController::add([
'code' => 'OK',
'objectType' => 'configurations',
'objectId' => $configuration['id'],
'type' => 'MODIFICATION',
'message' => "{configurationUpdated} : {$configuration['label']}",
'data' => ['identifier' => $configuration['identifier']]
]);
return $response->withStatus(204);
}
private static function checkMailer(array $args)
{
if (!Validator::stringType()->notEmpty()->validate($args['type'])) {
......
......@@ -532,12 +532,12 @@ class DocumentController
if (empty($workflow['userId'])) {
if (!empty($workflow['signaturePositions'])) {
foreach ($workflow['signaturePositions'] as $signaturePosition) {
$pageWidth = $pdf->getPageWidth($signaturePosition['page'] - 1);
$pageHeight = $pdf->getPageHeight($signaturePosition['page'] - 1);
$pageId = $pdf->importPage($signaturePosition['page']);
$dimensions = $pdf->getTemplateSize($pageId);
$llx = $pageWidth * $signaturePosition['positionX'] / 100;
$llx = $dimensions['width'] * $signaturePosition['positionX'] / 100;
$llx = round($llx);
$lly = $pageHeight - ($pageHeight * $signaturePosition['positionY'] / 100) - 40;
$lly = $dimensions['height'] - ($dimensions['height'] * $signaturePosition['positionY'] / 100) - 40;
$lly = round($lly);
$urx = $llx + 100;
$urx = round($urx);
......
......@@ -137,13 +137,8 @@ class EmailController
$phpmailer = new PHPMailer();
$phpmailer->setFrom($configuration['from']);
if ($configuration['type'] == 'smtp' || $configuration['type'] == 'mail') {
if ($configuration['type'] == 'smtp') {
$phpmailer->isSMTP();
} elseif ($configuration['type'] == 'mail') {
$phpmailer->isMail();
}
if ($configuration['type'] == 'smtp') {
$phpmailer->isSMTP();
$phpmailer->Host = $configuration['host'];
$phpmailer->Port = $configuration['port'];
$phpmailer->SMTPAutoTLS = false;
......@@ -161,6 +156,8 @@ class EmailController
$phpmailer->isSendmail();
} elseif ($configuration['type'] == 'qmail') {
$phpmailer->isQmail();
} elseif ($configuration['type'] == 'mail') {
$phpmailer->isMail();
}
$phpmailer->CharSet = $configuration['charset'];
......
......@@ -28,7 +28,7 @@ class PrivilegeController
['id' => 'manage_password_rules', 'type' => 'admin', 'icon' => 'lock-closed', 'route' => '/administration/passwordRules'],
['id' => 'manage_history', 'type' => 'admin', 'icon' => 'timer-outline', 'route' => '/administration/history'],
['id' => 'manage_otp_connectors', 'type' => 'admin', 'icon' => 'people-circle-outline', 'route' => '/administration/otps'],
['id' => 'manage_customization', 'type' => 'admin', 'icon' => 'color-wand-outline', 'route' => '/administration/customization'],
['id' => 'manage_customization', 'type' => 'admin', 'icon' => 'color-wand-outline', 'route' => '/administration/customization'],
['id' => 'manage_documents', 'type' => 'simple'],
['id' => 'indexation', 'type' => 'simple']
];
......
......@@ -46,7 +46,7 @@ class UserController
{
$queryParams = $request->getQueryParams();
$select = ['id', 'firstname', 'lastname', 'substitute', 'x509_fingerprint'];
$select = ['id', 'firstname', 'lastname', 'email', 'phone', 'substitute', 'x509_fingerprint'];
$where = [];
$queryData = [];
if (empty($queryParams['mode'])) {
......@@ -55,7 +55,6 @@ class UserController
}
if (PrivilegeController::hasPrivilege(['userId' => $GLOBALS['id'], 'privilege' => 'manage_users'])) {
$select[] = 'login';
$select[] = 'email';
}
$users = UserModel::get([
......@@ -140,7 +139,7 @@ class UserController
if (!empty($existingUser)) {
return $response->withStatus(400)->withJson(['errors' => 'Login already exists', 'lang' => 'userLoginAlreadyExists']);
}
$body['x509_fingerprint'] = $body['x509Fingerprint'];
if (!empty($body['isRest'])) {
......@@ -163,7 +162,7 @@ class UserController
}
}
} else {
$body['signatureModes'] = [];
$body['signatureModes'] = ['stamp'];
}
$body['signatureModes'] = json_encode($body['signatureModes']);
......@@ -214,6 +213,7 @@ class UserController
'firstname' => $body['firstname'],
'lastname' => $body['lastname'],
'email' => $body['email'],
'phone' => $body['phone'],
'signature_modes' => []
];
......@@ -366,14 +366,14 @@ class UserController
'where' => ['main_document_id in (?)', "process_date IS NULL", "status IS NULL"],
'data' => [$mainDocumentId]
]);
$workflowsId = array_column($workflows, 'id');
WorkflowModel::update([
'set' => ['status' => 'STOP', 'process_date' => 'CURRENT_TIMESTAMP'],
'where' => ['id in (?)', "process_date IS NULL AND status IS NULL"],
'data' => [$workflowsId]
]);
$previousDocumentId = null;
foreach ($workflows as $step) {
$document = DocumentModel::getById(['select' => ['typist', 'id'], 'id' => $step['main_document_id']]);
......@@ -748,7 +748,7 @@ class UserController
ValidatorModel::notEmpty($args, ['id']);
ValidatorModel::intVal($args, ['id']);
$user = UserModel::getById(['select' => ['id', 'login', 'email', 'firstname', 'lastname', 'picture', 'preferences', 'substitute', '"isRest"', 'signature_modes', 'x509_fingerprint'], 'id' => $args['id']]);
$user = UserModel::getById(['select' => ['id', 'login', 'email', 'firstname', 'lastname', 'phone', 'picture', 'preferences', 'substitute', '"isRest"', 'signature_modes', 'x509_fingerprint'], 'id' => $args['id']]);
if (empty($user)) {
return [];
}
......
......@@ -80,7 +80,7 @@ class UserModel
public static function create(array $args)
{
ValidatorModel::notEmpty($args, ['login', 'email', 'firstname', 'lastname', 'picture']);
ValidatorModel::stringType($args, ['login', 'email', 'firstname', 'lastname', 'picture', 'mode', 'signatureModes', 'x509_fingerprint']);
ValidatorModel::stringType($args, ['login', 'email', 'firstname', 'lastname', 'phone', 'picture', 'mode', 'signatureModes', 'x509_fingerprint']);
if (empty($args['password'])) {
$args['password'] = AuthenticationModel::generatePassword();
......@@ -96,6 +96,7 @@ class UserModel
'password' => $args['password'],
'firstname' => $args['firstname'],
'lastname' => $args['lastname'],
'phone' => $args['phone'],
'"isRest"' => empty($args['isRest']) ? 'false' : 'true',
'picture' => $args['picture'],
'password_modification_date' => 'CURRENT_TIMESTAMP',
......
......@@ -51,12 +51,16 @@ class AuthenticationController
$mailServerOnline = !empty($emailConfiguration['online']);
$customization = ConfigurationModel::getByIdentifier(['identifier' => 'customization', 'select' => ['value']]);
$customization = !empty($customization[0]['value']) ? json_decode($customization[0]['value'], true) : null;
return $response->withJson([
'connection' => $connection,
'changeKey' => $encryptKey == 'Security Key Maarch Parapheur #2008',
'instanceId' => $hashedPath,
'coreUrl' => $coreUrl,
'mailServerOnline' => $mailServerOnline,
'loginMessage' => $customization['loginMessage']
]);
}
......
......@@ -15,7 +15,9 @@
</ion-label>
</ion-list-header>
<ion-item lines="none">
<textarea style="padding-top: 10px;width: 100%;" name="login_message" id="login_message" [(ngModel)]="authService.loginMessage"></textarea>
<textarea style="padding-top: 10px;width: 100%;" name="login_message" id="login_message"
[value]="loginMessage" [(ngModel)]="loginMessage">
</textarea>
</ion-item>
<ion-item text-center lines="none" style="position: sticky;bottom:0px;z-index:1;">
<div style="display: flex;align-items: center;justify-content: center;width: 100%;background: white;">
......
......@@ -20,7 +20,7 @@ export class CustomizationComponent implements OnInit {
@ViewChild('customizationForm', { static: false }) customizationForm: NgForm;
loading: boolean = true;
loginMessage: string = '<span style="font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, Oxygen, Ubuntu, Cantarell, \'Open Sans\', \'Helvetica Neue\', sans-serif;">D&eacute;couvrez&nbsp;</span><strong style="font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, Oxygen, Ubuntu, Cantarell, \'Open Sans\', \'Helvetica Neue\', sans-serif;">Maarch Parapheur 21.03</strong><span style="font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, Oxygen, Ubuntu, Cantarell, \'Open Sans\', \'Helvetica Neue\', sans-serif;">&nbsp;avec&nbsp;</span><a style="font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, Oxygen, Ubuntu, Cantarell, \'Open Sans\', \'Helvetica Neue\', sans-serif;" title="notre guide de visite" href="https://docs.maarch.org/" target="_blank" rel="noopener"><span style="color: #f99830;"><strong>notre guide de visite en ligne</strong></span></a><span style="font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, Oxygen, Ubuntu, Cantarell, \'Open Sans\', \'Helvetica Neue\', sans-serif;">.</span>';
loginMessage: string = '';
constructor(
public http: HttpClient,
......@@ -30,24 +30,48 @@ export class CustomizationComponent implements OnInit {
private functions: FunctionsService
) { }
ngOnInit(){
// await this.authService.getLoginMessage();
setTimeout(() => {
this.initMce();
}, 100);
this.loading = false;
async ngOnInit(){
await this.getLginMessage();
}
ngOnDestroy(): void {
tinymce.remove();
}
getLginMessage() {
return new Promise((resolve) => {
this.http.get('../rest/authenticationInformations').pipe(
tap((data: any) => {
this.loginMessage = data.loginMessage;
setTimeout(() => {
this.initMce();
}, 100);
this.loading = false;
resolve(true);
}),
catchError((err: any) => {
this.notificationService.error(err);
return of(false);
})
).subscribe();
})
}
onSubmit() {
this.loginMessage = tinymce.get('login_message').getContent();
console.log(this.loginMessage);
// this.http.post('../res/', this.loginMessage).pipe(
// tap(() => {}),
// catchError((err: any) => {
// this.notificationService.handleErrors(err);
// return of(false);
// })
// ).subscribe();
if (this.functions.empty(this.loginMessage)) {
this.notificationService.error(this.translate.instant('lang.requiredField'));
} else {
this.http.put('../rest/customization', {id: this.authService.user.id , loginMessage: this.loginMessage}).pipe(
tap(() => {
this.notificationService.success(this.translate.instant('lang.messageSaved'));
}),
catchError((err: any) => {
this.notificationService.handleErrors(err);
return of(false);
})
).subscribe();
}
}
initMce() {
......
......@@ -7,7 +7,7 @@
<ion-title>{{title}}</ion-title>
</ion-toolbar>
</ion-header>
<form style="display: contents;" id="adminForm" (ngSubmit)="onSubmit()" #adminForm="ngForm">
<form *ngIf="!loading" style="display: contents;" id="adminForm" (ngSubmit)="onSubmit()" #adminForm="ngForm">
<ion-content>
<ion-item>
<ion-label color="secondary" position="floating">{{'lang.label' | translate}} *</ion-label>
......
......@@ -22,7 +22,7 @@
<ion-item>
<ion-label color="secondary" position="floating">{{'lang.smtpAuth' | translate}} *
</ion-label>
<ion-select name="SMTPSecure" [disabled]="['smtp', 'mail'].indexOf(sendmail.type) == -1"
<ion-select name="SMTPSecure" [disabled]="sendmail.type != 'smtp'"
[(ngModel)]="sendmail.secure" [value]="sendmail.secure" interface="popover" required>
<ion-select-option *ngFor="let security of smtpSecList" [value]="security.id">
{{security.label | translate}}
......@@ -33,7 +33,7 @@
<ion-col size="8">
<ion-item>
<ion-label color="secondary" position="floating">{{'lang.host' | translate}} *</ion-label>
<ion-input name="host" [disabled]="['smtp', 'mail'].indexOf(sendmail.type) == -1"
<ion-input name="host" [disabled]="sendmail.type != 'smtp'"
[(ngModel)]="sendmail.host" required>
</ion-input>
</ion-item>
......@@ -41,7 +41,7 @@
<ion-col size="2">
<ion-item>
<ion-label color="secondary" position="floating">{{'lang.port' | translate}} *</ion-label>
<ion-input type="number" name="port" [disabled]="['smtp', 'mail'].indexOf(sendmail.type) == -1"
<ion-input type="number" name="port" [disabled]="sendmail.type != 'smtp'"
[(ngModel)]="sendmail.port" required>
</ion-input>
</ion-item>
......@@ -50,14 +50,14 @@
</ion-grid>
<ion-item>
<ion-toggle name="SMTPAuth" [(ngModel)]="sendmail.auth" [checked]="sendmail.auth"
[disabled]="['smtp', 'mail'].indexOf(sendmail.type) == -1" (ngModelChange)="cleanAuthInfo()">
[disabled]="sendmail.type != 'smtp'" (ngModelChange)="cleanAuthInfo()">
</ion-toggle>
<ion-label>{{'lang.enableAuth' | translate}}</ion-label>
</ion-item>
<ion-item>
<ion-label color="secondary" position="floating">{{'lang.id' | translate}} *</ion-label>
<ion-input name="user" [(ngModel)]="sendmail.user"
[disabled]="!sendmail.auth || ['smtp', 'mail'].indexOf(sendmail.type) == -1" required>
[disabled]="!sendmail.auth || sendmail.type != 'smtp'" required>
</ion-input>
</ion-item>
<ion-item style="align-items: center;">
......@@ -68,14 +68,14 @@
<ion-label color="secondary" position="floating">{{passwordLanguage}}
{{!sendmail.passwordAlreadyExists ? '*' : ''}}</ion-label>
<ion-input name="password" [type]="hidePassword ? 'password' : 'text'" [(ngModel)]="sendmail.password"
[disabled]="!sendmail.auth || ['smtp', 'mail'].indexOf(sendmail.type) == -1"
[disabled]="!sendmail.auth || sendmail.type != 'smtp'"
[required]="!sendmail.passwordAlreadyExists">
</ion-input>
</ion-item>
<ion-item>
<ion-label color="secondary" position="floating">{{'lang.mailFrom' | translate}}</ion-label>
<ion-input name="mailFrom" [(ngModel)]="sendmail.from"
[disabled]="['smtp', 'mail'].indexOf(sendmail.type) == -1"
[disabled]="sendmail.type != 'smtp'"
pattern="(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)">
</ion-input>
</ion-item>
......
......@@ -53,6 +53,11 @@
<ion-input type="email" name="email" [maxlength]="128" [(ngModel)]="user.email" required
pattern="(^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$)"></ion-input>
</ion-item>
<ion-item>
<ion-label color="secondary" position="floating">{{'lang.phoneAlt' | translate}} *</ion-label>
<ion-input type="text" name="phone" [(ngModel)]="user.phone" required
pattern="^(?:0|\(?\+33\)?\s?|0033\s?)[1-79](?:[\.\-\s]?\d\d){4}$"></ion-input>
</ion-item>
<ion-item>
<ion-label>{{'lang.restUser' | translate}}</ion-label>
<ion-toggle slot="start" color="primary" [disabled]="!creationMode" name="isRest" [(ngModel)]="user.isRest"
......
......@@ -18,6 +18,7 @@ export interface User {
lastname: string;
login: string;
email: string;
phone: string;
picture: string;
isRest: boolean;
signatureModes: string[];
......@@ -40,6 +41,7 @@ export class UserComponent implements OnInit {
lastname: '',
login: '',
email: '',
phone: '',
picture: '',
isRest: false,
signatureModes: ['stamp'],
......@@ -99,6 +101,7 @@ export class UserComponent implements OnInit {
lastname: '',
login: '',
email: '',
phone: '',
picture: '',
signatureModes: ['stamp'],
isRest: false,
......
......@@ -9,6 +9,11 @@
</ion-toolbar>
</ion-header>
<ion-content *ngIf="!loading">
<ion-card *ngIf="data?.type === undefined">
<ion-item color="primary">
<ion-label class="info" [innerHTML]="'lang.internalUserOtpMsg' | translate : { user : data.firstname + ' ' + data.lastname}"></ion-label>
</ion-item>
</ion-card>
<ion-card *ngIf="sources.length > 1">
<ion-item>
<ion-label color="secondary">{{'lang.source' | translate}}</ion-label>
......
......@@ -46,8 +46,16 @@ export class OtpCreateComponent implements OnInit {
this.http.get('../rest/connectors').pipe(
tap((data: any) => {
this.sources = data.otp;
this.currentSource = this.data ? this.data : this.sources[0];
this.connectorId = this.data ? this.currentSource.sourceId : this.currentSource.id;
if (this.data?.type === undefined) {
this.currentSource = this.sources[0];
this.connectorId = this.currentSource.id;
} else {
this.currentSource = {
sourceId : this.data.sourceId,
type: this.data.type
};
this.connectorId = this.currentSource.sourceId;
}
this.loading = false;
resolve(true);
}),
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment