Refactoring
This commit is contained in:
parent
065161f994
commit
4bc04f5b27
30 changed files with 3710 additions and 0 deletions
68
templates/admin.html
Normal file
68
templates/admin.html
Normal file
|
@ -0,0 +1,68 @@
|
|||
{{define "title"}}Home{{end}}
|
||||
|
||||
{{define "admenu"}}
|
||||
{{end}}
|
||||
|
||||
{{define "body"}}
|
||||
<div class="alert alert-info">
|
||||
Bienvenue, <strong>{{ .Login.Login.WelcomeName }}</strong> !
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Mon compte
|
||||
</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a class="list-group-item list-group-item-action" href="/user">Modifier mon profil</a>
|
||||
<a class="list-group-item list-group-item-action" href="/passwd">Modifier mon mot de passe</a>
|
||||
<!--
|
||||
<a class="list-group-item list-group-item-action" href="/directory">Annuaire</a>
|
||||
-->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="mt-3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
Garage
|
||||
</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a class="list-group-item list-group-item-action" href="/garage/key">Mes identifiants</a>
|
||||
<a class="list-group-item list-group-item-action" href="/garage/website">Mes sites webs</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
{{if .Common.CanInvite}}
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
Outils
|
||||
</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<!-- <a class="list-group-item list-group-item-action" href="/user/send_code">Envoyer un code d'invitation</a> -->
|
||||
<a class="list-group-item list-group-item-action" href="/user/new">Créer un nouveau compte directement</a>
|
||||
<a class="list-group-item list-group-item-action" href="/SOGo">Accéder à mes courriels, mon agenda et à mes contacts</a>
|
||||
<a class="list-group-item list-group-item-action" href="https://www.lesgrandsvoisins.com/resdigita">Accéder au numérique des Grands Voisins .com</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .Common.CanAdmin}}
|
||||
<div class="card mt-3">
|
||||
<div class="card-header">
|
||||
Administration
|
||||
</div>
|
||||
<div class="list-group list-group-flush">
|
||||
<a class="list-group-item list-group-item-action" href="/admin/activate">Activer des Utilisateur·ices</a>
|
||||
<a class="list-group-item list-group-item-action" href="/admin/users">Utilisateur·ices</a>
|
||||
<a class="list-group-item list-group-item-action" href="/admin/groups">Groupes</a>
|
||||
<!-- <a class="list-group-item list-group-item-action" href="/admin/mailing">Mailing lists</a> -->
|
||||
<a class="list-group-item list-group-item-action" href="/admin/ldap/{{.BaseDN}}">Explorateur LDAP</a>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{end}}
|
39
templates/admin/activate.html
Normal file
39
templates/admin/activate.html
Normal file
|
@ -0,0 +1,39 @@
|
|||
{{define "title"}}Activer des utilisateurs |{{end}}
|
||||
|
||||
{{define "admenu"}}
|
||||
{{end}}
|
||||
|
||||
{{define "body"}}
|
||||
<table class="table mt-4">
|
||||
<thead>
|
||||
<th scope="col"></th>
|
||||
<th scope="col">Login</th>
|
||||
<th scope="col">Email</th>
|
||||
<th scope="col">Nom d'ffichage</th>
|
||||
<th scope="col">Prénom</th>
|
||||
<th scope="col">Nom de famille</th>
|
||||
<th scope="col">description</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{with $root := .}}
|
||||
{{range $user := $root.Users}}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/admin/activate/{{ $user.GetAttributeValue "cn" }}">
|
||||
Activer
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/admin/ldap/{{$user.DN}}">{{$user.GetAttributeValue "cn"}}</a>
|
||||
</td>
|
||||
<td>{{$user.GetAttributeValue "mail"}}</td>
|
||||
<td>{{$user.GetAttributeValue "displayName"}}</td>
|
||||
<td>{{$user.GetAttributeValue "givenName"}}</td>
|
||||
<td>{{$user.GetAttributeValue "sn"}}</td>
|
||||
<td>{{$user.GetAttributeValue "description"}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{end}}
|
96
templates/admin/create.html
Normal file
96
templates/admin/create.html
Normal file
|
@ -0,0 +1,96 @@
|
|||
{{define "title"}}Nouvel objet |{{end}}
|
||||
|
||||
{{define "admenu"}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{define "body"}}
|
||||
<div class="d-flex">
|
||||
<h2>Créer un objet</h2>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
{{range .Path}}
|
||||
<li class="breadcrumb-item"><a href="/admin/ldap/{{.DN}}">{{.Identifier}}</a></li>
|
||||
{{end}}
|
||||
<li class="breadcrumb-item active" aria-current="page">Nouvel objet</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
{{if .Common.Error}}
|
||||
<div class="alert alert-danger mt-4">Impossible de créer l'objet.
|
||||
<div style="font-size: 0.8em">{{ .Common.Error }}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<form method="POST" class="mt-4">
|
||||
<!--
|
||||
<div class="form-group">
|
||||
<label>Parent:</label>
|
||||
<input type="text" disabled="true" class="form-control" value="{{ .SuperDN }}" />
|
||||
</div>
|
||||
-->
|
||||
{{if eq .Template "ml"}}
|
||||
<div class="form-group">
|
||||
<label for="idvalue">Adresse complète de la mailing list :</label>
|
||||
<input type="text" id="idvalue" name="idvalue" class="form-control" value="{{ .IdValue }}" placeholder="exemple@resdigita.org" pattern="^[-A-z0-9\.]+@resdigita.org" />
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="form-group">
|
||||
<label for="idvalue">Identifiant :</label>
|
||||
<input type="text" id="idvalue" name="idvalue" class="form-control" value="{{ .IdValue }}" placeholder="exemple@resdigita.org" pattern="^[-A-z0-9\.]+@resdigita.org" />
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="form-group">
|
||||
<label for="idtype">Type d'identifiant :</label>
|
||||
<input type="text" {{if .Template}}disabled="disabled"{{end}} id="idtype" name="idtype" class="form-control" value="{{ .IdType }}" />
|
||||
</div>
|
||||
{{ if eq .Template "user" }}
|
||||
<div class="form-group">
|
||||
<label for="displayname">Nom affiché :</label>
|
||||
<input type="text" id="displayname" name="displayname" class="form-control" value="{{ .DisplayName }}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="mail">Email:</label>
|
||||
<input type="text" id="mail" name="mail" class="form-control" value="{{ .Mail }}" />
|
||||
</div>
|
||||
<input type="hidden" name="mail" value="" />
|
||||
<div class="form-group">
|
||||
<label for="givenname">Prénom :</label>
|
||||
<input type="text" id="givenname" name="givenname" class="form-control" value="{{ .GivenName }}" />
|
||||
</div>
|
||||
<input type="hidden" name="givenname" value="" />
|
||||
<div class="form-group">
|
||||
<label for="sn">Nom de Famille :</label>
|
||||
<input type="text" id="sn" name="sn" class="form-control" value=" {{ .SN }}" />
|
||||
</div>
|
||||
<input type="hidden" name="sn" value="" />
|
||||
{{ else }}
|
||||
<div class="form-group">
|
||||
<label for="description">Description :</label>
|
||||
<input type="text" id="description" name="description" class="form-control" value="{{ .Description }}" />
|
||||
</div>
|
||||
<input type="hidden" name="description" value="" />
|
||||
<div class="form-group">
|
||||
<label for="member">Member :</label>
|
||||
<input type="text" id="member" name="member" class="form-control" value="{{ .Member }}" />
|
||||
</div>
|
||||
<input type="hidden" name="member" value="" />
|
||||
{{ end }}
|
||||
<!--
|
||||
<div class="form-group">
|
||||
<label for="soc">StructuralObjectClass :</label>
|
||||
<input type="text" {{if .Template}}disabled="disabled"{{end}} id="soc" name="soc" class="form-control" value="{{ .StructuralObjectClass }}" />
|
||||
</div>
|
||||
-->
|
||||
<div class="form-group">
|
||||
<label for="oc">ObjectClass :</label>
|
||||
<textarea rows="5" {{if .Template}}disabled="disabled"{{end}} id="oc" name="oc" class="form-control">{{ .ObjectClass }}</textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Créer l'objet</button>
|
||||
</form>
|
||||
|
||||
{{end}}
|
40
templates/admin/groups.html
Normal file
40
templates/admin/groups.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
{{define "title"}}Liste des groupes |{{end}}
|
||||
|
||||
{{define "admenu"}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{define "body"}}
|
||||
|
||||
<div class="d-flex">
|
||||
<h2>Liste des groupes</h2>
|
||||
<a class="ml-auto btn btn-success" href="/admin/create/group/{{.GroupBaseDN}}">Nouveau groupe</a>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning mt-4">
|
||||
Les groupes servent uniquement à contrôler l'accès à différentes fonctionalités de Deuxfleurs.
|
||||
Ce ne sont pas des <a href="/admin/mailing">mailing lists</a>.
|
||||
</div>
|
||||
|
||||
<table class="table mt-4">
|
||||
<thead>
|
||||
<th scope="col">Identifiant</th>
|
||||
<th scope="col">Nom complet</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{with $root := .}}
|
||||
{{range $group := $root.Groups}}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/admin/ldap/{{$group.DN}}">
|
||||
{{$group.GetAttributeValue $root.GroupNameAttr}}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{$group.GetAttributeValue "description"}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{end}}
|
239
templates/admin/ldap.html
Normal file
239
templates/admin/ldap.html
Normal file
|
@ -0,0 +1,239 @@
|
|||
{{define "title"}}Explorateur LDAP |{{end}}
|
||||
|
||||
{{define "admenu"}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{define "body"}}
|
||||
|
||||
<div class="d-flex">
|
||||
<h2>Explorateur LDAP</h2>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
{{range .Path}}
|
||||
{{if .Active}}
|
||||
<li class="breadcrumb-item active" aria-current="page">{{.Identifier}}</li>
|
||||
{{else}}
|
||||
<li class="breadcrumb-item"><a href="/admin/ldap/{{.DN}}">{{.Identifier}}</a></li>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<table class="table mt-4">
|
||||
<tbody>
|
||||
{{range .ChildrenOU}}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/admin/ldap/{{.DN}}">
|
||||
🗀 {{.Identifier}}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{.Name}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{range .ChildrenOther}}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/admin/ldap/{{.DN}}">
|
||||
{{.Identifier}}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{.Name}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{if .CanAddChild}}
|
||||
<div class="mt-2">
|
||||
<a class="btn btn-sm btn-success" href="/admin/create/user/{{.DN}}">+utilisateur</a>
|
||||
<a class="ml-4 btn btn-sm btn-success" href="/admin/create/group/{{.DN}}">+groupe</a>
|
||||
<a class="ml-4 btn btn-sm btn-success" href="/admin/create/ou/{{.DN}}">+ou</a>
|
||||
<a class="ml-4 btn btn-sm btn-success" href="/admin/create/generic/{{.DN}}">+objet</a>
|
||||
</div>
|
||||
<hr class="mt-4" />
|
||||
{{end}}
|
||||
|
||||
{{if .Common.Success}}
|
||||
<div class="alert alert-success mt-2">Modification enregistrée.</div>
|
||||
{{end}}
|
||||
{{if .Common.Error}}
|
||||
<div class="alert alert-danger mt-2">
|
||||
Impossible d'effectuer la modification.
|
||||
<div style="font-size: 0.8em">{{.Error}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<h5>Attributs</h5>
|
||||
<div class="container">
|
||||
{{range $key, $value := .Props}}
|
||||
{{if $value.Editable}}
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-3"><strong>{{$value.Name}}</strong></div>
|
||||
|
||||
<div class="col-md-7">
|
||||
<form method="POST">
|
||||
<div class="form-row">
|
||||
<input type="hidden" name="action" value="modify" />
|
||||
<input type="hidden" name="attr" value="{{$key}}" />
|
||||
<textarea name="values" rows="{{len $value.Values}}" class="form-control col-md-9">{{range $i, $x := $value.Values}}{{if $i}}{{"\n"}}{{end}}{{$x}}{{end}}</textarea>
|
||||
<div class="col-md-3">
|
||||
<input type="submit" value="Modifier" class="form-control btn btn-primary" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-md-1">
|
||||
{{if $value.Deletable}}
|
||||
<form method="POST" onsubmit="return confirm('Supprimer cet attribut ?');">
|
||||
<input type="hidden" name="action" value="delete" />
|
||||
<input type="hidden" name="attr" value="{{$key}}" />
|
||||
<input type="submit" value="Suppr." class="form-control btn btn-danger btn-sm" />
|
||||
</form>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{range $key, $value := .Props}}
|
||||
{{if not $value.Editable}}
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-3"><strong>{{$key}}</strong></div>
|
||||
<div class="col-md-9">
|
||||
{{range $value.Values}}
|
||||
{{if eq $key "creatorsname" "modifiersname" }}
|
||||
<div><a href="/admin/ldap/{{.}}">{{.}}</a></div>
|
||||
{{else}}
|
||||
<div>{{.}}</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
||||
<form method="POST">
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-3">
|
||||
<input type="hidden" name="action" value="add" />
|
||||
<input class="form-control" type="text" name="attr" placeholder="Ajouter un attribut..." />
|
||||
</div>
|
||||
<div class="col-md-7">
|
||||
<div class="form-row">
|
||||
<textarea name="values" placeholder="Valeur(s)..." rows="2" class="form-control col-md-9"></textarea>
|
||||
<div class="col-md-3">
|
||||
<input type="submit" value="Ajouter" class="form-control btn btn-success" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{{if .HasMembers}}
|
||||
<hr class="mt-4" />
|
||||
<h5 class="mt-4">Membres</h5>
|
||||
<div class="container">
|
||||
{{range .Members}}
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-3">
|
||||
<strong>{{.Name}}</strong>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<a href="/admin/ldap/{{.DN}}">{{.DN}}</a>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<form method="POST" onsubmit="return confirm('Supprimer du groupe ?');">
|
||||
<input type="hidden" name="action" value="delete-member" />
|
||||
<input type="hidden" name="member" value="{{.DN}}" />
|
||||
<input type="submit" value="Supprimer" class="form-control btn btn-danger btn-sm" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="add" />
|
||||
<input type="hidden" name="attr" value="member" />
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-3"><strong>Ajouter au groupe :</strong>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<input class="form-control" type="text" list="users" name="values" placeholder="Utilisateur..." />
|
||||
<datalist id="users">
|
||||
{{range .PossibleNewMembers}}
|
||||
<option value="{{.DN}}">{{.Name}}</option>
|
||||
{{end}}
|
||||
</datalist>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="submit" value="Ajouter" class="form-control btn btn-success btn-sm" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .HasGroups}}
|
||||
<hr class="mt-4" />
|
||||
<h5 class="mt-4">Membre de</h5>
|
||||
<div class="container">
|
||||
{{range .Groups}}
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-3">
|
||||
<strong>{{.Name}}</strong>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<a href="/admin/ldap/{{.DN}}">{{.DN}}</a>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<form method="POST" onsubmit="return confirm('Supprimer du groupe ?');">
|
||||
<input type="hidden" name="action" value="delete-from-group" />
|
||||
<input type="hidden" name="group" value="{{.DN}}" />
|
||||
<input type="submit" value="Supprimer" class="form-control btn btn-danger btn-sm" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="add-to-group" />
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-3"><strong>Nouveau groupe :</strong>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<input class="form-control" list="groups" type="text" name="group" placeholder="Groupe..." />
|
||||
<datalist id="groups">
|
||||
{{range .PossibleNewGroups}}
|
||||
<option value="{{.DN}}">{{.Name}}</option>
|
||||
{{end}}
|
||||
</datalist>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="submit" value="Ajouter" class="form-control btn btn-success btn-sm" />
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{if .CanDelete}}
|
||||
<hr class="mt-4">
|
||||
<h5 class="mt-4">Supprimer l'objet</h5>
|
||||
<div class="alert alert-danger">
|
||||
Attention, cette opération est irrévocable !
|
||||
</div>
|
||||
<form method="POST" onsubmit="return confirm('Supprimer cet objet DÉFINITIVEMENT ?');">
|
||||
<div class="form-row">
|
||||
<input type="hidden" name="action" value="delete-object" />
|
||||
<div class="col-sm-5"></div>
|
||||
<input type="submit" value="Supprimer l'objet" class="form-control btn btn-danger col-sm-2" />
|
||||
<div class="col-sm-5"></div>
|
||||
</div>
|
||||
</form>
|
||||
{{end}}
|
||||
|
||||
<hr class="mt-4" />
|
||||
|
||||
{{end}}
|
35
templates/admin/mailing.html
Normal file
35
templates/admin/mailing.html
Normal file
|
@ -0,0 +1,35 @@
|
|||
{{define "title"}}Mailing lists |{{end}}
|
||||
|
||||
{{define "admenu"}}
|
||||
{{end}}
|
||||
|
||||
{{define "body"}}
|
||||
|
||||
<div class="d-flex">
|
||||
<h4>Mailing lists</h4>
|
||||
<a class="ml-auto btn btn-success" href="/admin/create/ml/{{.MailingBaseDN}}">Nouvelle mailing list</a>
|
||||
<a class="ml-4 btn btn-info" href="/">Menu principal</a>
|
||||
</div>
|
||||
|
||||
<table class="table mt-4">
|
||||
<thead>
|
||||
<th scope="col">Adresse</th>
|
||||
<th scope="col">Description</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{with $root := .}}
|
||||
{{range $ml := $root.MailingLists}}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/admin/mailing/{{$ml.GetAttributeValue $root.MailingNameAttr}}">
|
||||
{{$ml.GetAttributeValue $root.MailingNameAttr}}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{$ml.GetAttributeValue "description"}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{end}}
|
117
templates/admin/mailing/list.html
Normal file
117
templates/admin/mailing/list.html
Normal file
|
@ -0,0 +1,117 @@
|
|||
{{define "title"}}ML {{.MailingList.GetAttributeValue .MailingNameAttr}} |{{end}}
|
||||
{{define "admenu"}}
|
||||
{{end}}
|
||||
{{define "body"}}
|
||||
|
||||
<div class="d-flex">
|
||||
<h2>ML {{.MailingList.GetAttributeValue .MailingNameAttr}}
|
||||
<a class="ml-auto btn btn-sm btn-dark" href="/admin/ldap/{{.MailingList.DN}}">Vue avancée</a>
|
||||
</h2>
|
||||
<a class="ml-auto btn btn-dark" href="/admin/mailing">Liste des ML</a>
|
||||
</div>
|
||||
|
||||
{{if .Common.Success}}
|
||||
<div class="alert alert-success mt-2">Modification enregistrée.</div>
|
||||
{{end}}
|
||||
{{if .Common.Error}}
|
||||
<div class="alert alert-danger mt-2">
|
||||
Impossible d'effectuer la modification.
|
||||
<div style="font-size: 0.8em">{{.Error}}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
{{with $desc := .MailingList.GetAttributeValue "description"}}{{if $desc}}
|
||||
<p class="mt-4">{{$desc}}</p>
|
||||
{{end}}{{end}}
|
||||
|
||||
<table class="table mt-4">
|
||||
<thead>
|
||||
<th scope="col">Adresse</th>
|
||||
<th scope="col">Nom</th>
|
||||
<th scope="col" style="width: 6em"></th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{with $root := .}}
|
||||
{{range $member := $root.Members}}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/admin/ldap/{{$member.DN}}">
|
||||
{{$member.GetAttributeValue "mail"}}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{$member.GetAttributeValue "displayname"}}</td>
|
||||
<td>
|
||||
<form method="POST" onsubmit="return confirm('Supprimer de la ML ?');">
|
||||
<input type="hidden" name="action" value="delete-member" />
|
||||
<input type="hidden" name="member" value="{{.DN}}" />
|
||||
<input type="submit" value="Suppr" class="form-control btn btn-danger btn-sm" />
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{end}}
|
||||
{{if not .Members}}
|
||||
<tr><td>(aucun abonné)</td></tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<hr class="mt-4" />
|
||||
<h5 class="mt-4">Ajouter un destinataire</h5>
|
||||
|
||||
<div class="container">
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="add-member" />
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-3"><strong>Utilisateur existant :</strong> </div>
|
||||
<div class="col-md-5">
|
||||
<input class="form-control" type="text" list="users" name="member" placeholder="Utilisateur..." />
|
||||
<datalist id="users">
|
||||
{{range .PossibleNewMembers}}
|
||||
{{if .GetAttributeValue "mail"}}
|
||||
<option value="{{.DN}}">{{if .GetAttributeValue "displayname"}}{{.GetAttributeValue "displayname"}} ({{.GetAttributeValue "mail" }}){{else}}{{.GetAttributeValue "mail"}}{{end}}</option>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</datalist>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="submit" value="Ajouter" class="form-control btn btn-success btn-sm" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{{if .AllowGuest}}
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-10">OU</div>
|
||||
</div>
|
||||
|
||||
<form method="POST">
|
||||
<input type="hidden" name="action" value="add-external" />
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-3"><strong>E-mail :</strong></div>
|
||||
<div class="col-md-5">
|
||||
<input class="form-control" type="text" name="mail" placeholder="machin@truc.net..." />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-3"><strong>Nom (optionnel) :</strong></div>
|
||||
<div class="col-md-5">
|
||||
<input class="form-control" type="text" name="displayname" placeholder="Machin Truc..." />
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<input type="submit" value="Ajouter" class="form-control btn btn-success btn-sm" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<small class="form-text text-muted col-md-10">
|
||||
Si un utilisateur existe déjà avec l'email spécifiée, celui-ci sera ajouté à la liste.
|
||||
Sinon, un utilisateur invité sera créé.
|
||||
</small>
|
||||
</div>
|
||||
</form>
|
||||
{{end}}
|
||||
</div>
|
||||
|
||||
{{end}}
|
43
templates/admin/users.html
Normal file
43
templates/admin/users.html
Normal file
|
@ -0,0 +1,43 @@
|
|||
{{define "title"}}Liste des utilisateurs |{{end}}
|
||||
|
||||
{{define "admenu"}}
|
||||
{{end}}
|
||||
|
||||
|
||||
{{define "body"}}
|
||||
|
||||
<div class="d-flex">
|
||||
<h2>Liste des utilisateurs</h2>
|
||||
<a class="ml-auto btn btn-success" href="/admin/create/user/{{.UserBaseDN}}">Nouvel utilisateur</a>
|
||||
</div>
|
||||
|
||||
<table class="table mt-4">
|
||||
<thead>
|
||||
<th scope="col"></th>
|
||||
<th scope="col">Identifiant</th>
|
||||
<th scope="col">Nom complet</th>
|
||||
<th scope="col">Email</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{with $root := .}}
|
||||
{{range $user := $root.Users}}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/admin/unactivate/{{ $user.GetAttributeValue "cn" }}">
|
||||
Dèsactiver
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/admin/ldap/{{$user.DN}}">
|
||||
{{$user.GetAttributeValue $root.UserNameAttr}}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{$user.GetAttributeValue "displayName"}}</td>
|
||||
<td>{{$user.GetAttributeValue "mail"}}</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{{end}}
|
23
templates/directory/results.html
Normal file
23
templates/directory/results.html
Normal file
|
@ -0,0 +1,23 @@
|
|||
{{if .Results}}
|
||||
{{range .Results}}
|
||||
<div class="card mt-4">
|
||||
<div class="card-body">
|
||||
<div class="float-right">
|
||||
{{if .ProfilePicture}}
|
||||
<a href="/picture/{{.ProfilePicture}}">
|
||||
<img src="/picture/{{.ProfilePicture}}-thumb"/>
|
||||
</a>
|
||||
{{else}}
|
||||
{{end}}
|
||||
</div>
|
||||
<h5 class="card-title">
|
||||
{{.DisplayName}}
|
||||
<code>{{.Id}}@</code>
|
||||
</h5>
|
||||
<p class="card-text">{{.Description}}</p>
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{else}}
|
||||
Aucun résultat.
|
||||
{{end}}
|
237
templates/garage/key.html
Normal file
237
templates/garage/key.html
Normal file
|
@ -0,0 +1,237 @@
|
|||
{{define "title"}}Profile |{{end}}
|
||||
|
||||
{{define "admenu"}}
|
||||
{{end}}
|
||||
|
||||
{{define "body"}}
|
||||
<div class="d-flex">
|
||||
<h4>Mes identifiants</h4>
|
||||
<a class="ml-auto btn btn-link" href="/garage/website">Mes sites webs</a>
|
||||
<a class="ml-4 btn btn-info" href="/">Menu principal</a>
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs" id="proto" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" id="s3-tab" data-toggle="tab" href="#s3" role="tab" aria-controls="s3" aria-selected="true">S3</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="sftp-tab" data-toggle="tab" href="#sftp" role="tab" aria-controls="sftp" aria-selected="false">SFTP</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content" id="protocols">
|
||||
<div class="tab-pane fade show active" id="s3" role="tabpanel" aria-labelledby="s3-tab">
|
||||
<table class="table mt-4">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Identifiant de clé</th>
|
||||
<td>{{ .Key.AccessKeyId }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Clé secrète</th>
|
||||
<td>{{ .Key.SecretAccessKey }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Région</th>
|
||||
<td>garage</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Endpoint URL</th>
|
||||
<td>https://garage.resdigita.org</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Type d'URL</th>
|
||||
<td>DNS et chemin (préférer chemin)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Signature</th>
|
||||
<td>Version 4</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>Configurer votre logiciel :</p>
|
||||
|
||||
<div class="accordion" id="softconfig">
|
||||
<div class="card">
|
||||
<div class="card-header" id="awscli-title">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#awscli" aria-expanded="false" aria-controls="awscli">
|
||||
awscli
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="awscli" class="collapse show" aria-labelledby="awscli-title" data-parent="#softconfig">
|
||||
<div class="card-body">
|
||||
<p>Créez un fichier nommé <code>~/.awsrc</code> :</p>
|
||||
<pre>
|
||||
export AWS_ACCESS_KEY_ID={{ .Key.AccessKeyId }}
|
||||
export AWS_SECRET_ACCESS_KEY={{ .Key.SecretAccessKey }}
|
||||
export AWS_DEFAULT_REGION='garage'
|
||||
|
||||
function aws { command aws --endpoint-url https://garage.resdigita.org $@ ; }
|
||||
aws --version
|
||||
</pre>
|
||||
<p>Ensuite vous pouvez utiliser awscli :</p>
|
||||
<pre>
|
||||
source ~/.awsrc
|
||||
aws s3 ls
|
||||
aws s3 ls s3://my-bucket
|
||||
aws s3 cp /tmp/a.txt s3://my-bucket
|
||||
...
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header" id="minio-title">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#minio" aria-expanded="true" aria-controls="minio">
|
||||
Minio CLI
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div id="minio" class="collapse" aria-labelledby="minio-title" data-parent="#softconfig">
|
||||
<div class="card-body">
|
||||
<p>Vous pouvez configurer Minio CLI avec cette commande :</p>
|
||||
<pre>
|
||||
mc alias set \
|
||||
garage \
|
||||
https://garage.resdigita.org \
|
||||
{{ .Key.AccessKeyId }} \
|
||||
{{ .Key.SecretAccessKey }} \
|
||||
--api S3v4
|
||||
</pre>
|
||||
<p>Et ensuite pour utiliser Minio CLI avec :</p>
|
||||
<pre>
|
||||
mc ls garage/
|
||||
mc cp /tmp/a.txt garage/my-bucket/a.txt
|
||||
...
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header" id="winscp-title">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link btn-block text-left" type="button" data-toggle="collapse" data-target="#winscp" aria-expanded="true" aria-controls="winscp">
|
||||
WinSCP
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div id="winscp" class="collapse" aria-labelledby="winscp-title" data-parent="#softconfig">
|
||||
<div class="card-body">
|
||||
Reportez vous <a href="">au guide</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header" id="hugo-title">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#hugo" aria-expanded="false" aria-controls="hugo">
|
||||
Hugo
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="hugo" class="collapse" aria-labelledby="hugo-title" data-parent="#softconfig">
|
||||
<div class="card-body">
|
||||
<p>Dans votre fichier <code>config.toml</code>, rajoutez :</p>
|
||||
<pre>
|
||||
[[deployment.targets]]
|
||||
URL = "s3://bucket?endpoint=garage.resdigita.org&s3ForcePathStyle=true&region=garage"
|
||||
</pre>
|
||||
<p>Assurez-vous d'avoir un fichier dans lequel les variables <code>AWS_ACCESS_KEY_ID</code> et <code>AWS_SECRET_ACCESS_KEY</code> sont définies,
|
||||
ici on suppose que vous avez suivi les instructions de l'outil awscli (ci-dessus) et que vous avez un fichier <code>~/.awsrc</code> qui défini ces variables.
|
||||
Ensuite : </p>
|
||||
<pre>
|
||||
source ~/.awsrc
|
||||
hugo deploy
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="card-header" id="publii-title">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#publii" aria-expanded="false" aria-controls="publii">
|
||||
Publii
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="publii" class="collapse" aria-labelledby="publii-title" data-parent="#softconfig">
|
||||
<div class="card-body">
|
||||
<em>Bientôt...</em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- sftp -->
|
||||
<div class="tab-pane fade" id="sftp" role="tabpanel" aria-labelledby="sftp-tab">
|
||||
<table class="table mt-4">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">Nom d'utilisateur-ice</th>
|
||||
<td>{{ .Login.Status.Info.Username }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Mot de passe</th>
|
||||
<td>(votre mot de passe guichet)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Hôte</th>
|
||||
<td>sftp://bagage.resdigita.org</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Port</th>
|
||||
<td>2222</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p>Configurer votre logiciel :</p>
|
||||
|
||||
<div class="accordion" id="softconfig2">
|
||||
<div class="card">
|
||||
<div class="card-header" id="filezilla-title">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#filezilla" aria-expanded="false" aria-controls="filezilla">
|
||||
scp
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="filezilla" class="collapse show" aria-labelledby="filezilla-title" data-parent="#softconfig">
|
||||
<div class="card-body">
|
||||
<p>Un exemple avec SCP :</p>
|
||||
<pre>
|
||||
scp -oHostKeyAlgorithms=+ssh-rsa -P2222 -r ./public {{ .Login.Status.Info.Username }}@bagage.resdigita.org:mon_bucket/
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-header" id="filezilla-title">
|
||||
<h2 class="mb-0">
|
||||
<button class="btn btn-link btn-block text-left collapsed" type="button" data-toggle="collapse" data-target="#filezilla" aria-expanded="false" aria-controls="filezilla">
|
||||
Filezilla
|
||||
</button>
|
||||
</h2>
|
||||
</div>
|
||||
<div id="filezilla" class="collapse" aria-labelledby="filezilla-title" data-parent="#softconfig">
|
||||
<div class="card-body">
|
||||
<em>Bientôt</em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{end}}
|
61
templates/garage/website/inspect.html
Normal file
61
templates/garage/website/inspect.html
Normal file
|
@ -0,0 +1,61 @@
|
|||
{{define "title"}}Inspecter le site web |{{end}}
|
||||
|
||||
{{define "admenu"}}
|
||||
{{end}}
|
||||
|
||||
{{define "body"}}
|
||||
<div class="d-flex">
|
||||
<h4>Inspecter le site web</h4>
|
||||
<a class="ml-auto btn btn-link" href="/garage/key">Mes identifiants</a>
|
||||
<a class="ml-4 btn btn-success" href="/garage/website/new">Nouveau site web</a>
|
||||
<a class="ml-4 btn btn-info" href="/garage/website">Mes sites webs</a>
|
||||
</div>
|
||||
|
||||
<table class="table mt-4">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">ID</th>
|
||||
<td>{{ .Bucket.Id }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">URLs</th>
|
||||
<td>
|
||||
{{ range $alias := .Bucket.GlobalAliases }}
|
||||
{{ if contains $alias "." }}
|
||||
https://{{ $alias }}
|
||||
{{ else }}
|
||||
https://{{ $alias }}.web.resdigita.org
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Document d'index</th>
|
||||
<td> {{ .IndexDoc }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Document d'erreur</th>
|
||||
<td>{{ .Common.ErrorDoc }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Nombre de fichiers</th>
|
||||
<td>{{ .Bucket.Objects }} / {{ .MaxObjects }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Espace utilisé</th>
|
||||
<td>{{ .Bucket.Bytes }} / {{ .MaxSize }} octets</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h4>Configurer le nom de domaine</h4>
|
||||
|
||||
{{ range $alias := .Bucket.GlobalAliases }}
|
||||
{{ if contains $alias "." }}
|
||||
<p> Le nom de domaine {{ $alias }} n'est pas géré par Deuxfleurs, il vous revient donc de configurer la zone DNS. Vous devez ajouter une entrée <code>CNAME garage.resdigita.org</code> ou <code>ALIAS garage.resdigita.org</code> auprès de votre hébergeur DNS, qui est souvent aussi le bureau d'enregistrement (eg. Gandi, GoDaddy, BookMyName, etc.).</p>
|
||||
{{ else }}
|
||||
<p> Le nom de domaine https://{{ $alias }}.web.resdigita.org est fourni par Deuxfleurs, il n'y a pas de configuration à faire.</p>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
|
||||
{{end}}
|
41
templates/garage/website/list.html
Normal file
41
templates/garage/website/list.html
Normal file
|
@ -0,0 +1,41 @@
|
|||
{{define "title"}}Sites webs |{{end}}
|
||||
|
||||
{{define "admenu"}}
|
||||
{{end}}
|
||||
|
||||
{{define "body"}}
|
||||
|
||||
<div class="d-flex">
|
||||
<h4>Sites webs</h4>
|
||||
<a class="ml-auto btn btn-link" href="/garage/key">Mes identifiants</a>
|
||||
<a class="ml-4 btn btn-success" href="/garage/website/new">Nouveau site web</a>
|
||||
<a class="ml-4 btn btn-info" href="/">Menu principal</a>
|
||||
</div>
|
||||
|
||||
<table class="table mt-4">
|
||||
<thead>
|
||||
<th scope="col">ID</th>
|
||||
<th scope="col">URLs</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range $buck := .Key.Buckets }}
|
||||
{{ if $buck.GlobalAliases }}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="/garage/website/b/{{$buck.Id}}">{{$buck.Id}}</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ range $alias := $buck.GlobalAliases }}
|
||||
{{ if contains $alias "." }}
|
||||
https://{{ $alias }}
|
||||
{{ else }}
|
||||
https://{{ $alias }}.web.resdigita.org
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</td>
|
||||
</tr>
|
||||
{{ end }}
|
||||
{{ end }}
|
||||
</tbody>
|
||||
</table>
|
||||
{{end}}
|
66
templates/garage/website/new.html
Normal file
66
templates/garage/website/new.html
Normal file
|
@ -0,0 +1,66 @@
|
|||
{{define "title"}}Créer un site web |{{end}}
|
||||
|
||||
{{define "admenu"}}
|
||||
{{end}}
|
||||
|
||||
{{define "body"}}
|
||||
<div class="d-flex">
|
||||
<h4>Créer un site web</h4>
|
||||
<a class="ml-auto btn btn-link" href="/garage/key">Mes identifiants</a>
|
||||
<a class="ml-4 btn btn-info" href="/garage/website">Mes sites webs</a>
|
||||
</div>
|
||||
|
||||
<ul class="nav nav-tabs" id="proto" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" id="dnsint-tab" data-toggle="tab" href="#dnsint" role="tab" aria-controls="dnsint" aria-selected="true">Je n'ai pas de nom de domaine</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" id="dnsext-tab" data-toggle="tab" href="#dnsext" role="tab" aria-controls="dnsext" aria-selected="false">Utiliser mon propre nom de domaine</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content" id="protocols">
|
||||
<div class="tab-pane fade show active" id="dnsint" role="tabpanel" aria-labelledby="dnsint-tab">
|
||||
<form method="POST" class="mt-4">
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="bucket">Sous-domaine désiré :</label>
|
||||
<input type="text" id="bucket" name="bucket" placeholder="mon-site" class="form-control" value="" onkeyup="document.getElementById('url').value = `https://${document.getElementById('bucket').value}.web.resdigita.org`" />
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="url">Votre site sera accessible à l'URL suivante :</label>
|
||||
<input type="text" id="url" disabled="true" name="url" class="form-control" value="https://mon-site.web.resdigita.org" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<p>La première fois que vous chargerez votre site web, une erreur de certificat sera renvoyée. C'est normal, il faudra patienter quelques minutes le temps que le certificat se génère.</p>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Créer un nouveau site web</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="tab-pane fade show" id="dnsext" role="tabpanel" aria-labelledby="dnsext-tab">
|
||||
<form method="POST" class="mt-4">
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="bucket2">Votre nom de domaine :</label>
|
||||
<input type="text" id="bucket2" name="bucket2" placeholder="exemple.com" class="form-control" value="" onkeyup="document.getElementById('url2').value = `https://${document.getElementById('bucket2').value}`" />
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="url2">Votre site sera accessible à l'URL suivante :</label>
|
||||
<input type="text" id="url2" disabled="true" name="url2" class="form-control" value="https://exemple.com" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p>Vous devez éditer votre zone DNS, souvent gérée par votre bureau d'enregistrement, comme Gandi, pour la faire pointer vers Deuxfleurs. Si vous utilisez un sous domaine (eg. <code>site.exemple.com</code>), une entrée <code>CNAME</code> est appropriée :</p>
|
||||
<pre>site CNAME 3600 garage.resdigita.org.</pre>
|
||||
<p>Si vous utilisez la racine de votre nom de domaine (eg. <code>exemple.com</code>, aussi appelée APEX), la solution dépend de votre fournisseur DNS, il vous faudra au choix une entrée <code>ALIAS</code> ou <code>CNAME</code> en fonction de ce que votre fournisseur supporte :</p>
|
||||
<pre>@ ALIAS 3600 garage.resdigita.org.</pre>
|
||||
<p>La première fois que vous chargerez votre site web, une erreur de certificat sera renvoyée. C'est normal, il faudra patienter quelques minutes le temps que le certificat se génère.</p>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary">Créer un nouveau site web</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
40
templates/passwd/lost.html
Normal file
40
templates/passwd/lost.html
Normal file
|
@ -0,0 +1,40 @@
|
|||
{{define "title"}}G Pas (Je n'ai pas mon mal de passe){{end}}
|
||||
|
||||
{{define "admenu"}}
|
||||
{{end}}
|
||||
|
||||
{{define "body"}}
|
||||
<h2>G Pas</h2>
|
||||
|
||||
<p>Refaire son mot de passe</p>
|
||||
|
||||
{{if .Common.ErrorMessage}}
|
||||
<div class="alert alert-danger">Impossible
|
||||
<div style="font-size: 0.8em">{{ .Common.ErrorMessage }}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Common.Success}}
|
||||
<div class="alert alert-success mt-4">
|
||||
Email envoyé au courriel de secours.
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<p>Merci de renseigner au moins un des champs ci-dessous.</p>
|
||||
|
||||
<form method="POST">
|
||||
<div class="form-group">
|
||||
<label for="username">Ou identifiant :</label>
|
||||
<input type="text" name="username" id="username" class="form-control" value="{{ .Login.Username }}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="mail">ou mail (interne aux GV) :</label>
|
||||
<input type="text" name="mail" id="mail" class="form-control" value="{{ .Mail }}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="othermailbox">ou mail de secours :</label>
|
||||
<input type="text" name="othermailbox" id="othermailbox" class="form-control" value="{{ .OtherMailbox }}" />
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Refaire son mal de passe</button>
|
||||
</form>
|
||||
<script src="/static/javascript/minio.js"></script>
|
||||
{{end}}
|
13
templates/passwd/lost_password_email.txt
Normal file
13
templates/passwd/lost_password_email.txt
Normal file
|
@ -0,0 +1,13 @@
|
|||
From: {{.From}}
|
||||
To: {{.To}}
|
||||
Subject: Code d'invitation GVoisin.com
|
||||
Content-type: text/plain; charset=utf-8
|
||||
|
||||
Une refonte de mot de passe avait été demandé sur GVoisin.com
|
||||
|
||||
Pour créer votre compte, rendez-vous à l'adresse suivante:
|
||||
|
||||
{{.WebBaseAddress}}/passwd/lost/{{.Code}}
|
||||
|
||||
À bientôt sur GVoisin.com !
|
||||
|
86
templates/user.html
Normal file
86
templates/user.html
Normal file
|
@ -0,0 +1,86 @@
|
|||
{{define "title"}}Profile{{end}}
|
||||
|
||||
{{define "admenu"}}
|
||||
{{end}}
|
||||
|
||||
{{define "body"}}
|
||||
<div class="d-flex">
|
||||
<h2>Modifier mon profil</h2>
|
||||
</div>
|
||||
|
||||
{{if .Common.ErrorMessage}}
|
||||
<div class="alert alert-danger mt-4">Impossible d'effectuer la modification.
|
||||
<div style="font-size: 0.8em">{{ .Common.ErrorMessage }}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Common.Success}}
|
||||
<div class="alert alert-success mt-4">
|
||||
Profil enregistré.
|
||||
</div>
|
||||
{{end}}
|
||||
<form method="POST" class="mt-4" enctype="multipart/form-data">
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-6">
|
||||
<label>Identifiant:</label>
|
||||
<input type="text" disabled="true" class="form-control" value="{{ .Login.Status.Info.Username }}" />
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="mail">Adresse e-mail:</label>
|
||||
<input type="text" id="mail" disabled="true" name="mail" class="form-control" value="{{ .Mail }}" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="display_name">Nom complet:</label>
|
||||
<input type="text" id="display_name" name="display_name" class="form-control" value="{{ .DisplayName }}" />
|
||||
</div>
|
||||
|
||||
{{/*
|
||||
<!--
|
||||
<h4>Informations complémentaires</h4>
|
||||
{{if .ProfilePicture}}
|
||||
<div class="float-right">
|
||||
<a href="/picture/{{.ProfilePicture}}">
|
||||
<img src="/picture/{{.ProfilePicture}}-thumb" />
|
||||
</a>
|
||||
</div>
|
||||
{{end}}
|
||||
-->
|
||||
<!--
|
||||
<div class="form-group form-check">
|
||||
{{if .Visibility}}
|
||||
<input class="form-check-input" name="visibility" type="checkbox" id="visibility" value="on" checked>
|
||||
{{else}}
|
||||
<input class="form-check-input" name="visibility" type="checkbox" id="visibility">
|
||||
{{end}}
|
||||
<label class="form-check-label" for="visibility">Apparaître sur l'annuaire</label>
|
||||
</div>
|
||||
-->
|
||||
<!--
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-8 input-group mb-3 custom-file">
|
||||
<label for="image">Photo de profil:</label>
|
||||
<input type="file" name="image" class="custom-file-input" id="image">
|
||||
<label class="custom-file-label" for="image">Photo de profil (jpeg, jpg or png)</label>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
*/}}
|
||||
<div class="form-row">
|
||||
<div class="form-group col-md-6">
|
||||
<label for="given_name">Prénom:</label>
|
||||
<input type="text" id="given_name" name="given_name" class="form-control" value="{{ .GivenName }}" />
|
||||
</div>
|
||||
<div class="form-group col-md-6">
|
||||
<label for="surname">Nom de famille:</label>
|
||||
<input type="text" id="surname" name="surname" class="form-control" value="{{ .Surname }}" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="description">Description</label>
|
||||
<textarea id="description" name="description" class="form-control">{{ .Description }}</textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">Enregistrer les modifications</button>
|
||||
</form>
|
||||
<script src="/static/javascript/minio.js"></script>
|
||||
{{end}}
|
4
templates/user/code/invalid.html
Normal file
4
templates/user/code/invalid.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
{{define "title"}}Créer un compte |{{end}}
|
||||
|
||||
{{define "admenu"}}
|
||||
{{end}}
|
66
templates/user/code/send.html
Normal file
66
templates/user/code/send.html
Normal file
|
@ -0,0 +1,66 @@
|
|||
{{define "title"}}Envoyer un code d'invitation{{end}}
|
||||
{{define "admenu"}}
|
||||
{{end}}
|
||||
|
||||
{{define "body"}}
|
||||
<div class="d-flex">
|
||||
<h4>Envoyer un code d'invitation</h4>
|
||||
<a class="ml-auto btn btn-info" href="/">Retour</a>
|
||||
</div>
|
||||
|
||||
{{if .Common.ErrorMessage}}
|
||||
<div class="alert alert-danger mt-4">Impossible de génerer ou d'envoyer le code.
|
||||
<div style="font-size: 0.8em">{{ .Common.ErrorMessage }}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Common.Success}}
|
||||
<div class="alert alert-success mt-4">
|
||||
{{if .CodeSentTo}}
|
||||
Un code d'invitation a bien été envoyé à <code>{{ .CodeSentTo }}</code>.
|
||||
{{end}}
|
||||
{{if .CodeDisplay}}
|
||||
Lien d'invitation :
|
||||
|
||||
<p style="text-align: center; font-size: 1.4em" class="mt-4 mb-4">
|
||||
<a href="{{.WebBaseAddress}}/invitation/{{ .CodeDisplay }}">{{.WebBaseAddress}}/invitation/{{.CodeDisplay}}</a>
|
||||
</p>
|
||||
{{end}}
|
||||
</div>
|
||||
{{else}}
|
||||
<form method="POST" class="mt-4">
|
||||
Choisissez une option:
|
||||
|
||||
<div class="input-group mt-4">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<input type="radio" name="choice" value="send" id="choice_send" checked="true">
|
||||
</div>
|
||||
</div>
|
||||
<label class="form-control" for="choice_send">
|
||||
Envoyer le code à l'addresse suivante:
|
||||
</label>
|
||||
<input class="form-control" type="text" name="sendto" id="sendto" placeholder="Addresse mail..." onclick="document.getElementById('choice_send').checked = true;" />
|
||||
</div>
|
||||
{{if .Common.ErrorInvalidEmail}}
|
||||
<div class="alert alert-warning mt-4">
|
||||
Addresse mail invalide.
|
||||
</div>
|
||||
{{end}}
|
||||
|
||||
<div class="input-group mt-4">
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">
|
||||
<input type="radio" name="choice" value="display" id="choice_display">
|
||||
</div>
|
||||
</div>
|
||||
<label class="form-control" for="choice_display">
|
||||
Afficher le code et me laisser l'envoyer
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-group mt-4">
|
||||
<button type="submit" class="btn btn-primary">Génerer le code</button>
|
||||
</div>
|
||||
</form>
|
||||
{{end}}
|
||||
{{end}}
|
13
templates/user/mail.txt
Normal file
13
templates/user/mail.txt
Normal file
|
@ -0,0 +1,13 @@
|
|||
From: {{.From}}
|
||||
To: {{.To}}
|
||||
Subject: Code d'invitation GVoisin.com
|
||||
Content-type: text/plain; charset=utf-8
|
||||
|
||||
Vous avez été invité à créer un compte sur GVoisin.com par {{.InviteFrom}} :)
|
||||
|
||||
Pour créer votre compte, rendez-vous à l'addresse suivante:
|
||||
|
||||
{{.WebBaseAddress}}/invitation/{{.Code}}
|
||||
|
||||
À bientôt sur GVoisin.com !
|
||||
|
131
templates/user/new.html
Normal file
131
templates/user/new.html
Normal file
|
@ -0,0 +1,131 @@
|
|||
{{define "title"}}Créer un compte{{end}}
|
||||
|
||||
{{define "admenu"}}
|
||||
{{end}}
|
||||
|
||||
{{define "body"}}
|
||||
<div class="d-flex">
|
||||
<h2>Création d'un nouveau compte</h2>
|
||||
</div>
|
||||
{{if .Common.ErrorMessage}}
|
||||
<div class="alert alert-danger mt-4">Impossible de créer le compte.
|
||||
<div style="font-size: 0.8em">{{ .Common.ErrorMessage }}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Common.WarningMessage}}
|
||||
<div class="alert alert-danger mt-4">Des erreurs se sont produites, le compte pourrait ne pas être totalement
|
||||
fonctionnel.
|
||||
<div style="font-size: 0.8em">{{ .Common.WarningMessage }}</div>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .Common.Success}}
|
||||
<div class="alert alert-success mt-4">
|
||||
Le compe a été créé !
|
||||
Rendez-vous <a href="/session/logout">sur la page d'accueil</a> pour vous connecter avec ce nouveau compte.
|
||||
</div>
|
||||
{{else}}
|
||||
<form method="POST" class="mt-4">
|
||||
<div class="form-group">
|
||||
<label for="givenname">Prénom :</label>
|
||||
<input type="text" id="givenname" name="givenname" class="form-control" value="{{ .GivenName }}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="surname">Nom de famille :</label>
|
||||
<input type="text" id="surname" name="surname" class="form-control" value="{{ .Surname }}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="otheremail">Email de secours:</label>
|
||||
<input type="text" id="otheremail" name="otheremail" class="form-control" value="{{ .OtherEmail }}" />
|
||||
<small class="form-text text-muted">
|
||||
Le courriel de l'utilisateur.
|
||||
</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="displayname">Nom affiché :</label>
|
||||
<input type="text" id="displayname" name="displayname" class="form-control" value="{{ .DisplayName }}" />
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="username">Identifiant :</label>
|
||||
<input type="text" id="username" name="username" class="form-control" />
|
||||
<small class="form-text text-muted">
|
||||
Votre identifiant doit être en minuscule.
|
||||
</small>
|
||||
</div>
|
||||
<div id="calc-uid"></div>
|
||||
<div id="calc-cn"></div>
|
||||
{{if .ErrorInvalidUsername}}
|
||||
<div class="alert alert-warning">
|
||||
Nom d'utilisateur invalide. Ne peut contenir que les caractères suivants : chiffres, lettres minuscules, point,
|
||||
tiret bas (_) et tiret du milieu (-).
|
||||
</div>
|
||||
{{end}}
|
||||
{{if .ErrorUsernameTaken}}
|
||||
<div class="alert alert-warning">
|
||||
Ce nom d'utilisateur est déjà pris.
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="form-group">
|
||||
<label for="mail">Email des GV:</label>
|
||||
<input type="text" id="mail" name="mail" class="form-control" value="{{ .Mail }}" />
|
||||
<small class="form-text text-muted">
|
||||
Le courriel et login interne.
|
||||
</small>
|
||||
</div>
|
||||
<p><a href="#" onClick="javascript:var suggestPW = Math.random().toString(36).slice(-10); document.getElementById('password').value='{{ .SuggestPW }}';document.getElementById('password2').value='{{ .SuggestPW }}';">Utiliser ce mot de passe :</a> {{ .SuggestPW }}</p>
|
||||
<div class="form-group">
|
||||
<label for="password">Mot de passe :</label>
|
||||
<input type="password" id="password" name="password" class="form-control" />
|
||||
<small class="form-text text-muted">
|
||||
La seule contrainte est que votre mot de passe doit faire au moins 8 caractères. Utilisez chiffres, majuscules, et
|
||||
caractères spéciaux sans modération !
|
||||
</small>
|
||||
</div>
|
||||
{{if .ErrorPasswordTooShort}}
|
||||
<div class="alert alert-warning">
|
||||
Le mot de passe choisi est trop court (minimum 8 caractères).
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="form-group">
|
||||
<label for="password2">Répéter le mot de passe :</label>
|
||||
<input type="password" id="password2" name="password2" class="form-control" />
|
||||
</div>
|
||||
{{if .ErrorPasswordMismatch}}
|
||||
<div class="alert alert-warning">
|
||||
Les deux mots de passe entrés ne correspondent pas.
|
||||
</div>
|
||||
{{end}}
|
||||
<button type="submit" class="btn btn-primary">Créer le compte</button>
|
||||
</form>
|
||||
|
||||
<script lang="javascript">
|
||||
function changeGivenNameOrSurname () {
|
||||
givenname = document.getElementById("givenname");
|
||||
surname = document.getElementById("surname");
|
||||
displayname = document.getElementById("displayname");
|
||||
username = document.getElementById("username");
|
||||
displayname.value = givenname.value + " " + surname.value
|
||||
changeDisplayname();
|
||||
}
|
||||
function changeDisplayname () {
|
||||
givenname = document.getElementById("givenname");
|
||||
surname = document.getElementById("surname");
|
||||
displayname = document.getElementById("displayname");
|
||||
username = document.getElementById("username");
|
||||
username.value = displayname.value.toLowerCase().replace(/[^A-z0-9.-]/g, '').replace(/^[.-]/, '').replace(/[.-]$/, '');
|
||||
changeUsername();
|
||||
}
|
||||
function changeUsername () {
|
||||
givenname = document.getElementById("givenname");
|
||||
surname = document.getElementById("surname");
|
||||
displayname = document.getElementById("displayname");
|
||||
username = document.getElementById("username");
|
||||
mail = document.getElementById("mail");
|
||||
mail.value = username.value + "@lesgv.com";
|
||||
}
|
||||
document.getElementById("givenname").addEventListener("change",changeGivenNameOrSurname);
|
||||
document.getElementById("surname").addEventListener("change",changeGivenNameOrSurname);
|
||||
document.getElementById("displayname").addEventListener("change",changeDisplayname);
|
||||
document.getElementById("username").addEventListener("change",changeUsername);
|
||||
</script>
|
||||
{{end}}
|
||||
{{end}}
|
53
utils-config.go
Normal file
53
utils-config.go
Normal file
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
config handles reading the config.json file at the root and processing the settings
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
var configFlag = flag.String("config", "./config.json", "Configuration file path")
|
||||
|
||||
var config *ConfigFile
|
||||
|
||||
func readConfig() ConfigFile {
|
||||
// Default configuration values for certain fields
|
||||
config_file := ConfigFile{
|
||||
HttpBindAddr: ":9991",
|
||||
LdapServerAddr: "ldap://127.0.0.1:389",
|
||||
|
||||
UserNameAttr: "uid",
|
||||
GroupNameAttr: "gid",
|
||||
|
||||
InvitationNameAttr: "cn",
|
||||
InvitedAutoGroups: []string{},
|
||||
|
||||
Org: "ResDigita",
|
||||
}
|
||||
|
||||
_, err := os.Stat(*configFlag)
|
||||
if os.IsNotExist(err) {
|
||||
log.Fatalf("Could not find Guichet configuration file at %s. Please create this file, for exemple starting with config.json.exemple and customizing it for your deployment.", *configFlag)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
bytes, err := ioutil.ReadFile(*configFlag)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(bytes, &config_file)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return config_file
|
||||
}
|
40
utils-http.go
Normal file
40
utils-http.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
http-utils provide utility functions that interact with http
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
func logRequest(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func ldapOpen(w http.ResponseWriter) (*ldap.Conn, error) {
|
||||
if config.LdapTLS {
|
||||
tlsConf := &tls.Config{
|
||||
ServerName: config.LdapServerAddr,
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
return ldap.DialTLS("tcp", net.JoinHostPort(config.LdapServerAddr, "636"), tlsConf)
|
||||
} else {
|
||||
return ldap.DialURL("ldap://" + config.LdapServerAddr)
|
||||
}
|
||||
|
||||
// if err != nil {
|
||||
// http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
// log.Printf(fmt.Sprintf("27: %v %v", err, l))
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// return l
|
||||
}
|
17
utils-ldap.go
Normal file
17
utils-ldap.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
Utilities related to LDAP
|
||||
*/
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
func replaceIfContent(modifReq *ldap.ModifyRequest, key string, value string, previousValue string) error {
|
||||
if value != "" {
|
||||
modifReq.Replace(key, []string{value})
|
||||
} else if previousValue != "" {
|
||||
modifReq.Delete(key, []string{previousValue})
|
||||
}
|
||||
return nil
|
||||
}
|
10
utils-ssha.go
Normal file
10
utils-ssha.go
Normal file
|
@ -0,0 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/jsimonetti/pwscheme/ssha512"
|
||||
)
|
||||
|
||||
// Encode encodes the []byte of raw password
|
||||
func SSHAEncode(rawPassPhrase string) (string, error) {
|
||||
return ssha512.Generate(rawPassPhrase, 16)
|
||||
}
|
1021
view-admin.go
Normal file
1021
view-admin.go
Normal file
File diff suppressed because it is too large
Load diff
40
view-home.go
Normal file
40
view-home.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
home show the home page
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import "net/http"
|
||||
|
||||
func handleHome(w http.ResponseWriter, r *http.Request) {
|
||||
templateHome := getTemplate("home.html")
|
||||
|
||||
login := checkLogin(w, r)
|
||||
if login == nil {
|
||||
status := handleLogin(w, r)
|
||||
if status == nil {
|
||||
return
|
||||
}
|
||||
login = checkLogin(w, r)
|
||||
}
|
||||
|
||||
can_admin := false
|
||||
if login != nil {
|
||||
can_admin = login.Common.CanAdmin
|
||||
}
|
||||
|
||||
data := HomePageData{
|
||||
Login: NestedLoginTplData{
|
||||
Login: login,
|
||||
},
|
||||
BaseDN: config.BaseDN,
|
||||
Org: config.Org,
|
||||
Common: NestedCommonTplData{
|
||||
CanAdmin: can_admin,
|
||||
CanInvite: true,
|
||||
LoggedIn: true,
|
||||
},
|
||||
}
|
||||
templateHome.Execute(w, data)
|
||||
|
||||
}
|
555
view-invite.go
Normal file
555
view-invite.go
Normal file
|
@ -0,0 +1,555 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
// "github.com/emersion/go-sasl"
|
||||
// "github.com/emersion/go-smtp"
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
"github.com/gorilla/mux"
|
||||
"golang.org/x/crypto/argon2"
|
||||
)
|
||||
|
||||
var EMAIL_REGEXP = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||
|
||||
func checkInviterLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
|
||||
|
||||
login := checkLogin(w, r)
|
||||
if login == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// if !login.CanInvite {
|
||||
// http.Error(w, "Not authorized to invite new users.", http.StatusUnauthorized)
|
||||
// return nil
|
||||
// }
|
||||
|
||||
return login
|
||||
}
|
||||
|
||||
// New account creation directly from interface
|
||||
|
||||
func openNewUserLdap(config *ConfigFile) (*ldap.Conn, error) {
|
||||
l, err := openLdap(config)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("openNewUserLdap 1 : %v %v", err, l))
|
||||
log.Printf(fmt.Sprintf("openNewUserLdap 1 : %v", config))
|
||||
// data.Common.ErrorMessage = err.Error()
|
||||
}
|
||||
err = l.Bind(config.NewUserDN, config.NewUserPassword)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("openNewUserLdap 2 : %v", err))
|
||||
log.Printf(fmt.Sprintf("openNewUserLdap 2 : %v", config.NewUserDN))
|
||||
log.Printf(fmt.Sprintf("openNewUserLdap 2 : %v", config.NewUserPassword))
|
||||
log.Printf(fmt.Sprintf("openNewUserLdap 2 : %v", config))
|
||||
// data.Common.ErrorMessage = err.Error()
|
||||
}
|
||||
return l, err
|
||||
}
|
||||
|
||||
func handleLostPassword(w http.ResponseWriter, r *http.Request) {
|
||||
templateLostPasswordPage := getTemplate("passwd/lost.html")
|
||||
if checkLogin(w, r) != nil {
|
||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||
}
|
||||
|
||||
data := PasswordLostData{
|
||||
Common: NestedCommonTplData{
|
||||
CanAdmin: false,
|
||||
LoggedIn: false},
|
||||
}
|
||||
|
||||
if r.Method == "POST" {
|
||||
r.ParseForm()
|
||||
data.Username = strings.TrimSpace(strings.Join(r.Form["username"], ""))
|
||||
data.Mail = strings.TrimSpace(strings.Join(r.Form["mail"], ""))
|
||||
data.OtherMailbox = strings.TrimSpace(strings.Join(r.Form["othermailbox"], ""))
|
||||
user := User{
|
||||
CN: strings.TrimSpace(strings.Join(r.Form["username"], "")),
|
||||
UID: strings.TrimSpace(strings.Join(r.Form["username"], "")),
|
||||
Mail: strings.TrimSpace(strings.Join(r.Form["mail"], "")),
|
||||
OtherMailbox: strings.TrimSpace(strings.Join(r.Form["othermailbox"], "")),
|
||||
}
|
||||
ldapConn, err := openNewUserLdap(config)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("handleLostPassword 99 : %v %v", err, ldapConn))
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
}
|
||||
err = passwordLost(user, config, ldapConn)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("handleLostPassword 104 : %v %v", err, ldapConn))
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
} else {
|
||||
err = ldapConn.Bind(config.NewUserDN, config.NewUserPassword)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("handleLostPassword 109 : %v %v", err, ldapConn))
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
} else {
|
||||
data.Common.Success = true
|
||||
}
|
||||
}
|
||||
}
|
||||
data.Common.CanAdmin = false
|
||||
templateLostPasswordPage.Execute(w, data)
|
||||
}
|
||||
|
||||
func handleInviteNewAccount(w http.ResponseWriter, r *http.Request) {
|
||||
l, err := ldapOpen(w)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("58: %v %v", err, l))
|
||||
}
|
||||
// l.Bind(config.NewUserDN, config.NewUserPassword)
|
||||
|
||||
// login := checkInviterLogin(w, r)
|
||||
// if login == nil {
|
||||
// return
|
||||
// }
|
||||
// l, _ := ldap.DialURL(config.LdapServerAddr)
|
||||
// l.Bind(config.NewUserDN, config.NewUserPassword)
|
||||
|
||||
// loginInfo, err := doLogin(w, r, "testuser", config.NewUserDN, config.NewUserPassword)
|
||||
|
||||
// if err != nil {
|
||||
// log.Printf(fmt.Sprintf("58: %v %v", err, l))
|
||||
// }
|
||||
|
||||
// l := ldapOpen(w)
|
||||
if l == nil {
|
||||
return
|
||||
}
|
||||
|
||||
err = l.Bind(config.NewUserDN, config.NewUserPassword)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("58: %v %v", err, l))
|
||||
}
|
||||
handleNewAccount(w, r, l, config.NewUserDN)
|
||||
}
|
||||
|
||||
// New account creation using code
|
||||
|
||||
func handleInvitationCode(w http.ResponseWriter, r *http.Request) {
|
||||
code := mux.Vars(r)["code"]
|
||||
code_id, code_pw := readCode(code)
|
||||
|
||||
// log.Printf(code_pw)
|
||||
|
||||
login := checkLogin(w, r)
|
||||
|
||||
// l := ldapOpen(w)
|
||||
// if l == nil {
|
||||
// return
|
||||
// }
|
||||
|
||||
inviteDn := config.InvitationNameAttr + "=" + code_id + "," + config.InvitationBaseDN
|
||||
err := login.conn.Bind(inviteDn, code_pw)
|
||||
if err != nil {
|
||||
templateInviteInvalidCode := getTemplate("user/code/invalid.html")
|
||||
templateInviteInvalidCode.Execute(w, nil)
|
||||
return
|
||||
}
|
||||
|
||||
sReq := ldap.NewSearchRequest(
|
||||
inviteDn,
|
||||
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
|
||||
fmt.Sprintf("(objectclass=*)"),
|
||||
[]string{"dn", "creatorsname"},
|
||||
nil)
|
||||
sr, err := login.conn.Search(sReq)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if len(sr.Entries) != 1 {
|
||||
http.Error(w, fmt.Sprintf("Expected 1 entry, got %d", len(sr.Entries)), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
invitedBy := sr.Entries[0].GetAttributeValue("creatorsname")
|
||||
|
||||
if handleNewAccount(w, r, login.conn, invitedBy) {
|
||||
del_req := ldap.NewDelRequest(inviteDn, nil)
|
||||
err = login.conn.Del(del_req)
|
||||
if err != nil {
|
||||
log.Printf("Could not delete invitation %s: %s", inviteDn, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Common functions for new account
|
||||
|
||||
func handleNewAccount(w http.ResponseWriter, r *http.Request, l *ldap.Conn, invitedBy string) bool {
|
||||
templateInviteNewAccount := getTemplate("user/new.html")
|
||||
|
||||
data := &NewAccountData{}
|
||||
|
||||
if r.Method == "POST" {
|
||||
r.ParseForm()
|
||||
|
||||
newUser := User{}
|
||||
// login := checkLogin(w, r)
|
||||
|
||||
// newUser.Mail = fmt.Sprintf("%s@%s", strings.TrimSpace(strings.Join(r.Form["username"], "")), "lesgv.com")
|
||||
newUser.DisplayName = strings.TrimSpace(strings.Join(r.Form["displayname"], ""))
|
||||
newUser.GivenName = strings.TrimSpace(strings.Join(r.Form["givenname"], ""))
|
||||
newUser.SN = strings.TrimSpace(strings.Join(r.Form["surname"], ""))
|
||||
newUser.Mail = strings.TrimSpace(strings.Join(r.Form["mail"], ""))
|
||||
newUser.UID = strings.TrimSpace(strings.Join(r.Form["otheremail"], ""))
|
||||
newUser.CN = strings.TrimSpace(strings.Join(r.Form["username"], ""))
|
||||
newUser.DN = "cn=" + strings.TrimSpace(strings.Join(r.Form["username"], "")) + "," + config.InvitationBaseDN
|
||||
|
||||
password1 := strings.Join(r.Form["password"], "")
|
||||
password2 := strings.Join(r.Form["password2"], "")
|
||||
|
||||
if password1 != password2 {
|
||||
data.Common.Success = false
|
||||
data.ErrorPasswordMismatch = true
|
||||
} else {
|
||||
newUser.Password = password2
|
||||
l.Bind(config.NewUserDN, config.NewUserPassword)
|
||||
err := add(newUser, config, l)
|
||||
if err != nil {
|
||||
data.Common.Success = false
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
}
|
||||
http.Redirect(w, r, "/admin/activate", http.StatusFound)
|
||||
}
|
||||
|
||||
// tryCreateAccount(l, data, password1, password2, invitedBy)
|
||||
|
||||
} else {
|
||||
data.SuggestPW = fmt.Sprintf("%s", suggestPassword())
|
||||
}
|
||||
data.Common.CanAdmin = false
|
||||
data.Common.LoggedIn = false
|
||||
|
||||
templateInviteNewAccount.Execute(w, data)
|
||||
return data.Common.Success
|
||||
}
|
||||
|
||||
func tryCreateAccount(l *ldap.Conn, data *NewAccountData, pass1 string, pass2 string, invitedBy string) {
|
||||
checkFailed := false
|
||||
|
||||
// Check if username is correct
|
||||
if match, err := regexp.MatchString("^[a-z0-9._-]+$", data.Username); !(err == nil && match) {
|
||||
data.ErrorInvalidUsername = true
|
||||
checkFailed = true
|
||||
}
|
||||
|
||||
// Check if user exists
|
||||
userDn := config.UserNameAttr + "=" + data.Username + "," + config.UserBaseDN
|
||||
searchRq := ldap.NewSearchRequest(
|
||||
userDn,
|
||||
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
|
||||
"(objectclass=*)",
|
||||
[]string{"dn"},
|
||||
nil)
|
||||
|
||||
sr, err := l.Search(searchRq)
|
||||
if err != nil {
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
checkFailed = true
|
||||
}
|
||||
|
||||
if len(sr.Entries) > 0 {
|
||||
data.ErrorUsernameTaken = true
|
||||
checkFailed = true
|
||||
}
|
||||
|
||||
// Check that password is long enough
|
||||
if len(pass1) < 8 {
|
||||
data.ErrorPasswordTooShort = true
|
||||
checkFailed = true
|
||||
}
|
||||
|
||||
if pass1 != pass2 {
|
||||
data.ErrorPasswordMismatch = true
|
||||
checkFailed = true
|
||||
}
|
||||
|
||||
if checkFailed {
|
||||
return
|
||||
}
|
||||
|
||||
// Actually create user
|
||||
req := ldap.NewAddRequest(userDn, nil)
|
||||
req.Attribute("objectclass", []string{"inetOrgPerson", "organizationalPerson", "person", "top"})
|
||||
req.Attribute("structuralobjectclass", []string{"inetOrgPerson"})
|
||||
pw, err := SSHAEncode(pass1)
|
||||
if err != nil {
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
return
|
||||
}
|
||||
req.Attribute("userpassword", []string{pw})
|
||||
req.Attribute("invitedby", []string{invitedBy})
|
||||
if len(data.DisplayName) > 0 {
|
||||
req.Attribute("displayname", []string{data.DisplayName})
|
||||
}
|
||||
if len(data.GivenName) > 0 {
|
||||
req.Attribute("givenname", []string{data.GivenName})
|
||||
}
|
||||
if len(data.Surname) > 0 {
|
||||
req.Attribute("sn", []string{data.Surname})
|
||||
}
|
||||
if len(config.InvitedMailFormat) > 0 {
|
||||
email := strings.ReplaceAll(config.InvitedMailFormat, "{}", data.Username)
|
||||
req.Attribute("mail", []string{email})
|
||||
}
|
||||
|
||||
err = l.Add(req)
|
||||
if err != nil {
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
return
|
||||
}
|
||||
|
||||
for _, group := range config.InvitedAutoGroups {
|
||||
req := ldap.NewModifyRequest(group, nil)
|
||||
req.Add("member", []string{userDn})
|
||||
err = l.Modify(req)
|
||||
if err != nil {
|
||||
data.Common.WarningMessage += fmt.Sprintf("Cannot add to %s: %s\n", group, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
data.Common.Success = true
|
||||
}
|
||||
|
||||
// ---- Code generation ----
|
||||
|
||||
func handleInviteSendCode(w http.ResponseWriter, r *http.Request) {
|
||||
templateInviteSendCode := getTemplate("user/code/send.html")
|
||||
|
||||
login := checkInviterLogin(w, r)
|
||||
if login == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// carLicense
|
||||
|
||||
if r.Method == "POST" {
|
||||
r.ParseForm()
|
||||
data := &SendCodeData{
|
||||
WebBaseAddress: config.WebAddress,
|
||||
}
|
||||
|
||||
// modify_request := ldap.NewModifyRequest(login.UserEntry.DN, nil)
|
||||
// // choice := strings.Join(r.Form["choice"], "")
|
||||
// // sendto := strings.Join(r.Form["sendto"], "")
|
||||
code, code_id, code_pw := genCode()
|
||||
log.Printf(fmt.Sprintf("272: %v %v %v", code, code_id, code_pw))
|
||||
// // Create invitation object in database
|
||||
// modify_request.Add("carLicense", []string{fmt.Sprintf("%s,%s,%s",code, code_id, code_pw)})
|
||||
// err := login.conn.Modify(modify_request)
|
||||
// if err != nil {
|
||||
// data.Common.ErrorMessage = err.Error()
|
||||
// // return
|
||||
// } else {
|
||||
// data.Common.Success = true
|
||||
// data.CodeDisplay = code
|
||||
// }
|
||||
log.Printf(fmt.Sprintf("279: %v %v %v", code, code_id, code_pw))
|
||||
addReq := ldap.NewAddRequest("documentIdentifier="+code_id+","+config.InvitationBaseDN, nil)
|
||||
addReq.Attribute("objectClass", []string{"top", "document", "simpleSecurityObject"})
|
||||
addReq.Attribute("cn", []string{code})
|
||||
addReq.Attribute("userPassword", []string{code_pw})
|
||||
addReq.Attribute("documentIdentifier", []string{code_id})
|
||||
log.Printf(fmt.Sprintf("285: %v %v %v", code, code_id, code_pw))
|
||||
log.Printf(fmt.Sprintf("286: %v", addReq))
|
||||
err := login.conn.Add(addReq)
|
||||
if err != nil {
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
// return
|
||||
} else {
|
||||
data.Common.Success = true
|
||||
data.CodeDisplay = code
|
||||
}
|
||||
data.Common.CanAdmin = login.Common.CanAdmin
|
||||
|
||||
templateInviteSendCode.Execute(w, data)
|
||||
|
||||
// if choice == "display" || choice == "send" {
|
||||
// log.Printf(fmt.Sprintf("260: %v %v %v %v", login, choice, sendto, data))
|
||||
// trySendCode(login, choice, sendto, data)
|
||||
// }
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func trySendCode(login *LoginStatus, choice string, sendto string, data *SendCodeData) {
|
||||
log.Printf(fmt.Sprintf("269: %v %v %v %v", login, choice, sendto, data))
|
||||
// Generate code
|
||||
code, code_id, code_pw := genCode()
|
||||
log.Printf(fmt.Sprintf("272: %v %v %v", code, code_id, code_pw))
|
||||
// Create invitation object in database
|
||||
|
||||
// len_base_dn := len(strings.Split(config.BaseDN, ","))
|
||||
// dn_split := strings.Split(super_dn, ",")
|
||||
// for i := len_base_dn + 1; i <= len(dn_split); i++ {
|
||||
// path = append(path, PathItem{
|
||||
// DN: strings.Join(dn_split[len(dn_split)-i:len(dn_split)], ","),
|
||||
// Identifier: dn_split[len(dn_split)-i],
|
||||
// })
|
||||
// }
|
||||
// data := &SendCodeData{
|
||||
// WebBaseAddress: config.WebAddress,
|
||||
// }
|
||||
// // Handle data
|
||||
// data := &CreateData{
|
||||
// SuperDN: super_dn,
|
||||
// Path: path,
|
||||
// }
|
||||
// data.IdType = config.UserNameAttr
|
||||
// data.StructuralObjectClass = "inetOrgPerson"
|
||||
// data.ObjectClass = "inetOrgPerson\norganizationalPerson\nperson\ntop"
|
||||
// data.IdValue = strings.TrimSpace(strings.Join(r.Form["idvalue"], ""))
|
||||
// data.DisplayName = strings.TrimSpace(strings.Join(r.Form["displayname"], ""))
|
||||
// data.GivenName = strings.TrimSpace(strings.Join(r.Form["givenname"], ""))
|
||||
// data.Mail = strings.TrimSpace(strings.Join(r.Form["mail"], ""))
|
||||
// data.Member = strings.TrimSpace(strings.Join(r.Form["member"], ""))
|
||||
// data.Description = strings.TrimSpace(strings.Join(r.Form["description"], ""))
|
||||
// data.SN = strings.TrimSpace(strings.Join(r.Form["sn"], ""))
|
||||
// object_class := []string{}
|
||||
// for _, oc := range strings.Split(data.ObjectClass, "\n") {
|
||||
// x := strings.TrimSpace(oc)
|
||||
// if x != "" {
|
||||
// object_class = append(object_class, x)
|
||||
// }
|
||||
// }
|
||||
// dn := data.IdType + "=" + data.IdValue + "," + super_dn
|
||||
// req := ldap.NewAddRequest(dn, nil)
|
||||
// req.Attribute("objectclass", object_class)
|
||||
// // req.Attribute("mail", []string{data.IdValue})
|
||||
// /*
|
||||
// if data.StructuralObjectClass != "" {
|
||||
// req.Attribute("structuralobjectclass", []string{data.StructuralObjectClass})
|
||||
// }
|
||||
// */
|
||||
// if data.DisplayName != "" {
|
||||
// req.Attribute("displayname", []string{data.DisplayName})
|
||||
// }
|
||||
// if data.GivenName != "" {
|
||||
// req.Attribute("givenname", []string{data.GivenName})
|
||||
// }
|
||||
// if data.Mail != "" {
|
||||
// req.Attribute("mail", []string{data.Mail})
|
||||
// }
|
||||
// if data.Member != "" {
|
||||
// req.Attribute("member", []string{data.Member})
|
||||
// }
|
||||
// if data.SN != "" {
|
||||
// req.Attribute("sn", []string{data.SN})
|
||||
// }
|
||||
// if data.Description != "" {
|
||||
// req.Attribute("description", []string{data.Description})
|
||||
// }
|
||||
// err := login.conn.Add(req)
|
||||
// // log.Printf(fmt.Sprintf("899: %v",err))
|
||||
// // log.Printf(fmt.Sprintf("899: %v",req))
|
||||
// // log.Printf(fmt.Sprintf("899: %v",data))
|
||||
// if err != nil {
|
||||
// data.Common.Error = err.Error()
|
||||
// } else {
|
||||
// if template == "ml" {
|
||||
// http.Redirect(w, r, "/admin/mailing/"+data.IdValue, http.StatusFound)
|
||||
// } else {
|
||||
// http.Redirect(w, r, "/admin/ldap/"+dn, http.StatusFound)
|
||||
// }
|
||||
// }
|
||||
|
||||
// inviteDn := config.InvitationNameAttr + "=" + code_id + "," + config.InvitationBaseDN
|
||||
// req := ldap.NewAddRequest(inviteDn, nil)
|
||||
// pw, err := SSHAEncode(code_pw)
|
||||
// if err != nil {
|
||||
// data.Common.ErrorMessage = err.Error()
|
||||
// return
|
||||
// }
|
||||
// req.Attribute("employeeNumber", []string{pw})
|
||||
// req.Attribute("objectclass", []string{"top", "invitationCode"})
|
||||
|
||||
// err = login.conn.Add(req)
|
||||
// if err != nil {
|
||||
// log.Printf(fmt.Sprintf("286: %v", req))
|
||||
// data.Common.ErrorMessage = err.Error()
|
||||
// return
|
||||
// }
|
||||
|
||||
// If we want to display it, do so
|
||||
if choice == "display" {
|
||||
data.Common.Success = true
|
||||
data.CodeDisplay = code
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, we are sending a mail
|
||||
if !EMAIL_REGEXP.MatchString(sendto) {
|
||||
data.ErrorInvalidEmail = true
|
||||
return
|
||||
}
|
||||
|
||||
templateMail := template.Must(template.ParseFiles(templatePath + "/invite_mail.txt"))
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
templateMail.Execute(buf, &CodeMailFields{
|
||||
To: sendto,
|
||||
From: config.MailFrom,
|
||||
InviteFrom: login.WelcomeName(),
|
||||
Code: code,
|
||||
WebBaseAddress: config.WebAddress,
|
||||
})
|
||||
|
||||
log.Printf("Sending mail to: %s", sendto)
|
||||
// var auth sasl.Client = nil
|
||||
// if config.SMTPUsername != "" {
|
||||
// auth = sasl.NewPlainClient("", config.SMTPUsername, config.SMTPPassword)
|
||||
// }
|
||||
// err = smtp.SendMail(config.SMTPServer, auth, config.MailFrom, []string{sendto}, buf)
|
||||
// if err != nil {
|
||||
// data.Common.ErrorMessage = err.Error()
|
||||
// return
|
||||
// }
|
||||
// log.Printf("Mail sent.")
|
||||
|
||||
data.Common.Success = true
|
||||
data.CodeSentTo = sendto
|
||||
}
|
||||
|
||||
func genCode() (code string, code_id string, code_pw string) {
|
||||
random := make([]byte, 32)
|
||||
n, err := rand.Read(random)
|
||||
if err != nil || n != 32 {
|
||||
log.Fatalf("Could not generate random bytes: %s", err)
|
||||
}
|
||||
|
||||
a := binary.BigEndian.Uint32(random[0:4])
|
||||
b := binary.BigEndian.Uint32(random[4:8])
|
||||
c := binary.BigEndian.Uint32(random[8:12])
|
||||
|
||||
code = fmt.Sprintf("%03d-%03d-%03d", a%1000, b%1000, c%1000)
|
||||
code_id, code_pw = readCode(code)
|
||||
log.Printf(fmt.Sprintf("342: %v %v %v", code, code_id, code_pw))
|
||||
return code, code_id, code_pw
|
||||
}
|
||||
|
||||
func readCode(code string) (code_id string, code_pw string) {
|
||||
// Strip everything that is not a digit
|
||||
code_digits := ""
|
||||
for _, c := range code {
|
||||
if c >= '0' && c <= '9' {
|
||||
code_digits = code_digits + string(c)
|
||||
}
|
||||
}
|
||||
|
||||
id_hash := argon2.IDKey([]byte(code_digits), []byte("Guichet ID"), 2, 64*1024, 4, 32)
|
||||
pw_hash := argon2.IDKey([]byte(code_digits), []byte("Guichet PW"), 2, 64*1024, 4, 32)
|
||||
|
||||
code_id = hex.EncodeToString(id_hash[:8])
|
||||
code_pw = hex.EncodeToString(pw_hash[:16])
|
||||
return code_id, code_pw
|
||||
}
|
128
view-login.go
Normal file
128
view-login.go
Normal file
|
@ -0,0 +1,128 @@
|
|||
/*
|
||||
login handles login and current-user verification
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
)
|
||||
|
||||
func (login *LoginStatus) WelcomeName() string {
|
||||
ret := login.UserEntry.GetAttributeValue("givenName")
|
||||
if ret == "" {
|
||||
ret = login.UserEntry.GetAttributeValue("displayName")
|
||||
}
|
||||
if ret == "" {
|
||||
ret = login.Info.Username
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
err := logout(w, r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
http.Redirect(w, r, "/", http.StatusFound)
|
||||
}
|
||||
|
||||
func handleLogin(w http.ResponseWriter, r *http.Request) *LoginInfo {
|
||||
templateLogin := getTemplate("login.html")
|
||||
|
||||
if r.Method == "POST" {
|
||||
// log.Printf("%v", "Parsing Form handleLogin")
|
||||
r.ParseForm()
|
||||
|
||||
username := strings.Join(r.Form["username"], "")
|
||||
password := strings.Join(r.Form["password"], "")
|
||||
user_dn := fmt.Sprintf("%s=%s,%s", config.UserNameAttr, username, config.UserBaseDN)
|
||||
|
||||
// log.Printf("%v", user_dn)
|
||||
// log.Printf("%v", username)
|
||||
|
||||
if strings.EqualFold(username, config.AdminAccount) {
|
||||
user_dn = username
|
||||
}
|
||||
loginInfo, err := doLogin(w, r, username, user_dn, password)
|
||||
// log.Printf("%v", loginInfo)
|
||||
if err != nil {
|
||||
data := &LoginFormData{
|
||||
Username: username,
|
||||
Common: NestedCommonTplData{
|
||||
CanAdmin: false,
|
||||
CanInvite: true,
|
||||
LoggedIn: false,
|
||||
},
|
||||
}
|
||||
if ldap.IsErrorWithCode(err, ldap.LDAPResultInvalidCredentials) {
|
||||
data.WrongPass = true
|
||||
} else if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
|
||||
data.WrongUser = true
|
||||
} else {
|
||||
log.Printf("%v", err)
|
||||
log.Printf("%v", user_dn)
|
||||
log.Printf("%v", username)
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
}
|
||||
templateLogin.Execute(w, data)
|
||||
}
|
||||
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
|
||||
return loginInfo
|
||||
|
||||
} else if r.Method == "GET" {
|
||||
templateLogin.Execute(w, LoginFormData{
|
||||
Common: NestedCommonTplData{
|
||||
CanAdmin: false,
|
||||
CanInvite: true,
|
||||
LoggedIn: false}})
|
||||
return nil
|
||||
} else {
|
||||
http.Error(w, "Unsupported method", http.StatusBadRequest)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func doLogin(w http.ResponseWriter, r *http.Request, username string, user_dn string, password string) (*LoginInfo, error) {
|
||||
l, _ := ldapOpen(w)
|
||||
|
||||
err := l.Bind(user_dn, password)
|
||||
if err != nil {
|
||||
log.Printf("doLogin : %v", err)
|
||||
log.Printf("doLogin : %v", user_dn)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Successfully logged in, save it to session
|
||||
session, err := store.Get(r, SESSION_NAME)
|
||||
if err != nil {
|
||||
session, _ = store.New(r, SESSION_NAME)
|
||||
}
|
||||
|
||||
session.Values["login_username"] = username
|
||||
session.Values["login_password"] = password
|
||||
session.Values["login_dn"] = user_dn
|
||||
|
||||
err = session.Save(r, w)
|
||||
if err != nil {
|
||||
log.Printf("doLogin Session Save: %v", err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
LoginInfo := LoginInfo{
|
||||
DN: user_dn,
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
|
||||
return &LoginInfo, nil
|
||||
}
|
169
view-passwd.go
Normal file
169
view-passwd.go
Normal file
|
@ -0,0 +1,169 @@
|
|||
/*
|
||||
gpas is GVoisin password reset
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"log"
|
||||
|
||||
// "github.com/emersion/go-sasl"
|
||||
// "github.com/emersion/go-smtp"
|
||||
"net/smtp"
|
||||
|
||||
"github.com/go-ldap/ldap/v3"
|
||||
// "strings"
|
||||
b64 "encoding/base64"
|
||||
)
|
||||
|
||||
// type InvitationAccount struct {
|
||||
// UID string
|
||||
// Password string
|
||||
// BaseDN string
|
||||
// }
|
||||
|
||||
// var EMAIL_REGEXP := regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||
|
||||
func passwordLost(user User, config *ConfigFile, ldapConn *ldap.Conn) error {
|
||||
if user.CN == "" && user.Mail == "" && user.OtherMailbox == "" {
|
||||
return errors.New("Il n'y a pas de quoi identifier l'utilisateur")
|
||||
}
|
||||
searchFilter := "(|"
|
||||
if user.CN != "" {
|
||||
searchFilter += "(cn=" + user.UID + ")"
|
||||
}
|
||||
if user.Mail != "" {
|
||||
searchFilter += "(mail=" + user.Mail + ")"
|
||||
}
|
||||
if user.OtherMailbox != "" {
|
||||
searchFilter += "(carLicense=" + user.OtherMailbox + ")"
|
||||
}
|
||||
searchFilter += ")"
|
||||
searchReq := ldap.NewSearchRequest(config.UserBaseDN, ldap.ScopeSingleLevel, ldap.NeverDerefAliases, 0, 0, false, searchFilter, []string{"cn", "uid", "mail", "carLicense", "sn", "displayName", "givenName"}, nil)
|
||||
searchRes, err := ldapConn.Search(searchReq)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("passwordLost 49 : %v %v", err, ldapConn))
|
||||
log.Printf(fmt.Sprintf("passwordLost 50 : %v", searchReq))
|
||||
log.Printf(fmt.Sprintf("passwordLost 51: %v", user))
|
||||
return err
|
||||
}
|
||||
if len(searchRes.Entries) == 0 {
|
||||
log.Printf("Il n'y a pas d'utilisateur qui correspond %v", searchReq)
|
||||
return errors.New("Il n'y a pas d'utilisateur qui correspond")
|
||||
}
|
||||
// log.Printf(fmt.Sprintf("passwordLost 58 : %v", user))
|
||||
// log.Printf(fmt.Sprintf("passwordLost 59 : %v", searchRes.Entries[0]))
|
||||
// log.Printf(fmt.Sprintf("passwordLost 60 : %v", searchRes.Entries[0].GetAttributeValue("cn")))
|
||||
// log.Printf(fmt.Sprintf("passwordLost 61 : %v", searchRes.Entries[0].GetAttributeValue("uid")))
|
||||
// log.Printf(fmt.Sprintf("passwordLost 62 : %v", searchRes.Entries[0].GetAttributeValue("mail")))
|
||||
// log.Printf(fmt.Sprintf("passwordLost 63 : %v", searchRes.Entries[0].GetAttributeValue("carLicense")))
|
||||
// Préparation du courriel à envoyer
|
||||
user.Password = suggestPassword()
|
||||
code := b64.URLEncoding.EncodeToString([]byte(user.UID + ";" + user.Password))
|
||||
user.DN = "uid=" + searchRes.Entries[0].GetAttributeValue("cn") + ",ou=invitations,dc=resdigita,dc=org"
|
||||
user.UID = searchRes.Entries[0].GetAttributeValue("cn")
|
||||
user.CN = searchRes.Entries[0].GetAttributeValue("cn")
|
||||
user.Mail = searchRes.Entries[0].GetAttributeValue("mail")
|
||||
user.OtherMailbox = searchRes.Entries[0].GetAttributeValue("carLicense")
|
||||
/* Check for outstanding invitation */
|
||||
searchReq = ldap.NewSearchRequest(config.InvitationBaseDN, ldap.ScopeBaseObject,
|
||||
ldap.NeverDerefAliases, 0, 0, false, "(uid="+user.UID+")", []string{"seeAlso"}, nil)
|
||||
searchRes, err = ldapConn.Search(searchReq)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("passwordLost (Check existing invitation) : %v", err))
|
||||
log.Printf(fmt.Sprintf("passwordLost (Check existing invitation) : %v", user))
|
||||
return err
|
||||
}
|
||||
if len(searchRes.Entries) == 0 {
|
||||
/* Add the invitation */
|
||||
addReq := ldap.NewAddRequest(
|
||||
user.DN,
|
||||
nil)
|
||||
addReq.Attribute("objectClass", []string{"top", "account", "simpleSecurityObject"})
|
||||
addReq.Attribute("uid", []string{user.UID})
|
||||
addReq.Attribute("userPassword", []string{"absdefghi"})
|
||||
addReq.Attribute("seeAlso", []string{config.UserNameAttr + "=" + user.UID + "," + config.UserBaseDN})
|
||||
err = ldapConn.Add(addReq)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("passwordLost 83 : %v", err))
|
||||
log.Printf(fmt.Sprintf("passwordLost 84 : %v", user))
|
||||
// log.Printf(fmt.Sprintf("passwordLost 85 : %v", searchRes.Entries[0]))
|
||||
// For some reason I get here even if the entry exists already
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = passwd(user, config, ldapConn)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprintf("passwordLost 90 : %v", err))
|
||||
log.Printf(fmt.Sprintf("passwordLost 91 : %v", user))
|
||||
log.Printf(fmt.Sprintf("passwordLost 92 : %v", searchRes.Entries[0]))
|
||||
return err
|
||||
}
|
||||
templateMail := template.Must(template.ParseFiles(templatePath + "/lost_password_email.txt"))
|
||||
buf := bytes.NewBuffer([]byte{})
|
||||
templateMail.Execute(buf, &CodeMailFields{
|
||||
To: user.OtherMailbox,
|
||||
From: config.MailFrom,
|
||||
InviteFrom: user.UID,
|
||||
Code: code,
|
||||
WebBaseAddress: config.WebAddress,
|
||||
})
|
||||
// message := []byte("Hi " + user.OtherMailbox)
|
||||
log.Printf("Sending mail to: %s", user.OtherMailbox)
|
||||
// var auth sasl.Client = nil
|
||||
// if config.SMTPUsername != "" {
|
||||
// auth = sasl.NewPlainClient("", config.SMTPUsername, config.SMTPPassword)
|
||||
// }
|
||||
message := buf.Bytes()
|
||||
auth := smtp.PlainAuth("", config.SMTPUsername, config.SMTPPassword, config.SMTPServer)
|
||||
log.Printf("auth: %v", auth)
|
||||
err = smtp.SendMail(config.SMTPServer+":587", auth, config.SMTPUsername, []string{user.OtherMailbox}, message)
|
||||
if err != nil {
|
||||
log.Printf("email send error %v", err)
|
||||
return err
|
||||
}
|
||||
log.Printf("Mail sent.")
|
||||
return nil
|
||||
}
|
||||
|
||||
func passwordFound(user User, config *ConfigFile, ldapConn *ldap.Conn) (string, error) {
|
||||
l, err := openLdap(config)
|
||||
if err != nil {
|
||||
log.Printf("passwordFound %v", err)
|
||||
log.Printf("passwordFound Config : %v", config)
|
||||
return "", err
|
||||
}
|
||||
if user.DN == "" && user.UID != "" {
|
||||
user.DN = "uid=" + user.UID + ",ou=invitations,dc=resdigita,dc=org"
|
||||
}
|
||||
err = l.Bind(user.DN, user.Password)
|
||||
if err != nil {
|
||||
log.Printf("passwordFound %v", err)
|
||||
log.Printf("passwordFound %v", user.DN)
|
||||
log.Printf("passwordFound %v", user.UID)
|
||||
return "", err
|
||||
}
|
||||
searchReq := ldap.NewSearchRequest(user.DN, ldap.ScopeBaseObject,
|
||||
ldap.NeverDerefAliases, 0, 0, false, "(uid="+user.UID+")", []string{"seeAlso"}, nil)
|
||||
var searchRes *ldap.SearchResult
|
||||
searchRes, err = ldapConn.Search(searchReq)
|
||||
if err != nil {
|
||||
log.Printf("passwordFound %v", err)
|
||||
log.Printf("passwordFound %v", searchReq)
|
||||
log.Printf("passwordFound %v", ldapConn)
|
||||
log.Printf("passwordFound %v", searchRes)
|
||||
return "", err
|
||||
}
|
||||
if len(searchRes.Entries) == 0 {
|
||||
log.Printf("passwordFound %v", err)
|
||||
log.Printf("passwordFound %v", searchReq)
|
||||
log.Printf("passwordFound %v", ldapConn)
|
||||
log.Printf("passwordFound %v", searchRes)
|
||||
return "", err
|
||||
}
|
||||
return searchRes.Entries[0].GetAttributeValue("seeAlso"), err
|
||||
}
|
219
view-profile.go
Normal file
219
view-profile.go
Normal file
|
@ -0,0 +1,219 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
b64 "encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
func handleProfile(w http.ResponseWriter, r *http.Request) {
|
||||
templateProfile := getTemplate("profile.html")
|
||||
|
||||
login := checkLogin(w, r)
|
||||
if login == nil {
|
||||
templatePasswd := getTemplate("passwd.html")
|
||||
templatePasswd.Execute(w, PasswdTplData{
|
||||
|
||||
Common: NestedCommonTplData{
|
||||
CanAdmin: false,
|
||||
LoggedIn: false},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
data := &ProfileTplData{
|
||||
Login: NestedLoginTplData{
|
||||
Status: login,
|
||||
Login: login,
|
||||
},
|
||||
Common: NestedCommonTplData{
|
||||
CanAdmin: login.Common.CanAdmin,
|
||||
LoggedIn: true,
|
||||
ErrorMessage: "",
|
||||
Success: false,
|
||||
},
|
||||
}
|
||||
|
||||
data.Mail = login.UserEntry.GetAttributeValue("mail")
|
||||
data.DisplayName = login.UserEntry.GetAttributeValue("displayName")
|
||||
data.GivenName = login.UserEntry.GetAttributeValue("givenName")
|
||||
data.Surname = login.UserEntry.GetAttributeValue("sn")
|
||||
// data.Visibility = login.UserEntry.GetAttributeValue(FIELD_NAME_DIRECTORY_VISIBILITY)
|
||||
data.Description = login.UserEntry.GetAttributeValue("description")
|
||||
//data.ProfilePicture = login.UserEntry.GetAttributeValue(FIELD_NAME_PROFILE_PICTURE)
|
||||
|
||||
if r.Method == "POST" {
|
||||
//5MB maximum size files
|
||||
r.ParseMultipartForm(5 << 20)
|
||||
user := User{
|
||||
DN: login.Info.DN,
|
||||
// CN: ,
|
||||
GivenName: strings.TrimSpace(strings.Join(r.Form["given_name"], "")),
|
||||
DisplayName: strings.TrimSpace(strings.Join(r.Form["display_name"], "")),
|
||||
Mail: strings.TrimSpace(strings.Join(r.Form["mail"], "")),
|
||||
SN: strings.TrimSpace(strings.Join(r.Form["surname"], "")),
|
||||
//UID: ,
|
||||
Description: strings.TrimSpace(strings.Join(r.Form["description"], "")),
|
||||
// Password: ,
|
||||
}
|
||||
|
||||
if user.DisplayName != "" {
|
||||
err := modify(user, config, login.conn)
|
||||
if err != nil {
|
||||
data.Common.ErrorMessage = "handleProfile : " + err.Error()
|
||||
} else {
|
||||
data.Common.Success = true
|
||||
}
|
||||
}
|
||||
findUser, err := get(user, config, login.conn)
|
||||
if err != nil {
|
||||
data.Common.ErrorMessage = "handleProfile : " + err.Error()
|
||||
}
|
||||
data.DisplayName = findUser.DisplayName
|
||||
data.GivenName = findUser.GivenName
|
||||
data.Surname = findUser.SN
|
||||
data.Description = findUser.Description
|
||||
data.Mail = findUser.Mail
|
||||
data.Common.LoggedIn = false
|
||||
|
||||
/*
|
||||
visible := strings.TrimSpace(strings.Join(r.Form["visibility"], ""))
|
||||
if visible != "" {
|
||||
visible = "on"
|
||||
} else {
|
||||
visible = "off"
|
||||
}
|
||||
data.Visibility = visible
|
||||
*/
|
||||
/*
|
||||
profilePicture, err := uploadProfilePicture(w, r, login)
|
||||
if err != nil {
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
}
|
||||
if profilePicture != "" {
|
||||
data.ProfilePicture = profilePicture
|
||||
}
|
||||
*/
|
||||
|
||||
//modify_request.Replace(FIELD_NAME_DIRECTORY_VISIBILITY, []string{data.Visibility})
|
||||
//modify_request.Replace(FIELD_NAME_DIRECTORY_VISIBILITY, []string{"on"})
|
||||
//if data.ProfilePicture != "" {
|
||||
// modify_request.Replace(FIELD_NAME_PROFILE_PICTURE, []string{data.ProfilePicture})
|
||||
// }
|
||||
|
||||
// err := login.conn.Modify(modify_request)
|
||||
// log.Printf(fmt.Sprintf("Profile:079: %v",modify_request))
|
||||
// log.Printf(fmt.Sprintf("Profile:079: %v",err))
|
||||
// log.Printf(fmt.Sprintf("Profile:079: %v",data))
|
||||
// if err != nil {
|
||||
// data.Common.ErrorMessage = err.Error()
|
||||
// } else {
|
||||
// data.Common.Success = true
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
templateProfile.Execute(w, data)
|
||||
}
|
||||
|
||||
func handleFoundPassword(w http.ResponseWriter, r *http.Request) {
|
||||
templateFoundPasswordPage := getTemplate("passwd.html")
|
||||
data := PasswdTplData{
|
||||
Common: NestedCommonTplData{
|
||||
CanAdmin: false,
|
||||
LoggedIn: false},
|
||||
}
|
||||
code := mux.Vars(r)["code"]
|
||||
// code = strings.TrimSpace(strings.Join([]string{code}, ""))
|
||||
newCode, _ := b64.URLEncoding.DecodeString(code)
|
||||
ldapConn, err := openNewUserLdap(config)
|
||||
if err != nil {
|
||||
log.Printf(fmt.Sprint("handleFoundPassword / openNewUserLdap / %v", err))
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
}
|
||||
codeArray := strings.Split(string(newCode), ";")
|
||||
user := User{
|
||||
UID: codeArray[0],
|
||||
Password: codeArray[1],
|
||||
DN: "uid=" + codeArray[0] + ",ou=invitations,dc=resdigita,dc=org",
|
||||
}
|
||||
user.SeeAlso, err = passwordFound(user, config, ldapConn)
|
||||
if err != nil {
|
||||
log.Printf("handleFoundPassword / passwordFound %v", err)
|
||||
log.Printf("handleFoundPassword / passwordFound %v", err)
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
}
|
||||
if r.Method == "POST" {
|
||||
r.ParseForm()
|
||||
|
||||
password := strings.Join(r.Form["password"], "")
|
||||
password2 := strings.Join(r.Form["password2"], "")
|
||||
|
||||
if len(password) < 8 {
|
||||
data.TooShortError = true
|
||||
} else if password2 != password {
|
||||
data.NoMatchError = true
|
||||
} else {
|
||||
err := passwd(User{
|
||||
DN: user.SeeAlso,
|
||||
Password: password,
|
||||
}, config, ldapConn)
|
||||
if err != nil {
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
} else {
|
||||
data.Common.Success = true
|
||||
}
|
||||
}
|
||||
}
|
||||
data.Common.CanAdmin = false
|
||||
templateFoundPasswordPage.Execute(w, data)
|
||||
}
|
||||
|
||||
func handlePasswd(w http.ResponseWriter, r *http.Request) {
|
||||
templatePasswd := getTemplate("passwd.html")
|
||||
data := &PasswdTplData{
|
||||
Common: NestedCommonTplData{
|
||||
CanAdmin: false,
|
||||
LoggedIn: true,
|
||||
ErrorMessage: "",
|
||||
Success: false,
|
||||
},
|
||||
}
|
||||
|
||||
login := checkLogin(w, r)
|
||||
if login == nil {
|
||||
templatePasswd.Execute(w, data)
|
||||
return
|
||||
}
|
||||
data.Login.Status = login
|
||||
data.Common.CanAdmin = login.Common.CanAdmin
|
||||
|
||||
if r.Method == "POST" {
|
||||
r.ParseForm()
|
||||
|
||||
password := strings.Join(r.Form["password"], "")
|
||||
password2 := strings.Join(r.Form["password2"], "")
|
||||
|
||||
if len(password) < 8 {
|
||||
data.TooShortError = true
|
||||
} else if password2 != password {
|
||||
data.NoMatchError = true
|
||||
} else {
|
||||
err := passwd(User{
|
||||
DN: login.Info.DN,
|
||||
Password: password,
|
||||
}, config, login.conn)
|
||||
if err != nil {
|
||||
data.Common.ErrorMessage = err.Error()
|
||||
} else {
|
||||
data.Common.Success = true
|
||||
}
|
||||
}
|
||||
}
|
||||
data.Common.CanAdmin = false
|
||||
templatePasswd.Execute(w, data)
|
||||
}
|
Loading…
Reference in a new issue