admin: Handle team password
This commit is contained in:
parent
ed69dc6ba4
commit
5eeb1a6297
|
@ -254,7 +254,7 @@ func generateClientCert(_ httprouter.Params, _ []byte) (interface{}, error) {
|
||||||
serial := serial_b.Uint64()
|
serial := serial_b.Uint64()
|
||||||
|
|
||||||
// Let's pick a random password
|
// Let's pick a random password
|
||||||
password, err := pki.GeneratePassword()
|
password, err := fic.GeneratePassword()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,195 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"path"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"srs.epita.fr/fic-server/admin/pki"
|
||||||
|
"srs.epita.fr/fic-server/libfic"
|
||||||
|
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
var OidcSecret = ""
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
router.POST("/api/password", apiHandler(
|
||||||
|
func(httprouter.Params, []byte) (interface{}, error) {
|
||||||
|
if passwd, err := fic.GeneratePassword(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return map[string]string{"password": passwd}, nil
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
router.GET("/api/teams/:tid/password", apiHandler(teamHandler(
|
||||||
|
func(team fic.Team, _ []byte) (interface{}, error) {
|
||||||
|
return team.Password, nil
|
||||||
|
})))
|
||||||
|
router.POST("/api/teams/:tid/password", apiHandler(teamHandler(
|
||||||
|
func(team fic.Team, _ []byte) (interface{}, error) {
|
||||||
|
if passwd, err := fic.GeneratePassword(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
team.Password = &passwd
|
||||||
|
return team.Update()
|
||||||
|
}
|
||||||
|
})))
|
||||||
|
router.GET("/api/dex.yaml", apiHandler(
|
||||||
|
func(httprouter.Params, []byte) (interface{}, error) {
|
||||||
|
return genDexConfig()
|
||||||
|
}))
|
||||||
|
router.POST("/api/dex.yaml", apiHandler(
|
||||||
|
func(httprouter.Params, []byte) (interface{}, error) {
|
||||||
|
if dexcfg, err := genDexConfig(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if err := ioutil.WriteFile(path.Join(pki.PKIDir, "shared", "dex-config.yaml"), []byte(dexcfg), 0644); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
router.GET("/api/dex-password.tpl", apiHandler(
|
||||||
|
func(httprouter.Params, []byte) (interface{}, error) {
|
||||||
|
return genDexPasswordTpl()
|
||||||
|
}))
|
||||||
|
router.POST("/api/dex-password.tpl", apiHandler(
|
||||||
|
func(httprouter.Params, []byte) (interface{}, error) {
|
||||||
|
if dexcfg, err := genDexPasswordTpl(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if err := ioutil.WriteFile(path.Join(pki.PKIDir, "shared", "dex-password.tpl"), []byte(dexcfg), 0644); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const dexcfgtpl = `issuer: https://fic.srs.epita.fr
|
||||||
|
storage:
|
||||||
|
type: sqlite3
|
||||||
|
config:
|
||||||
|
file: /var/dex/dex.db
|
||||||
|
web:
|
||||||
|
http: 0.0.0.0:5556
|
||||||
|
frontend:
|
||||||
|
issuer: Challenge forensic
|
||||||
|
logoURL: img/fic.png
|
||||||
|
dir: /srv/dex/web/
|
||||||
|
oauth2:
|
||||||
|
skipApprovalScreen: true
|
||||||
|
staticClients:
|
||||||
|
{{ range $c := .Clients }}
|
||||||
|
- id: {{ $c.Id }}
|
||||||
|
name: {{ $c.Name }}
|
||||||
|
redirectURIs: [{{ range $u := $c.RedirectURIs }}'{{ $u }}'{{ end }}]
|
||||||
|
secret: {{ $c.Secret }}
|
||||||
|
{{ end }}
|
||||||
|
enablePasswordDB: true
|
||||||
|
staticPasswords:
|
||||||
|
{{ range $t := .Teams }}
|
||||||
|
- email: "team{{ printf "%02d" $t.Id }}"
|
||||||
|
hash: "{{with $t }}{{ .HashedPassword }}{{end}}"
|
||||||
|
{{ end }}
|
||||||
|
`
|
||||||
|
|
||||||
|
const dexpasswdtpl = `{{ "{{" }} template "header.html" . {{ "}}" }}
|
||||||
|
|
||||||
|
<div class="theme-panel">
|
||||||
|
<h2 class="theme-heading">
|
||||||
|
Bienvenue au challenge Forensic !
|
||||||
|
</h2>
|
||||||
|
<form method="post" action="{{ "{{" }} .PostURL {{ "}}" }}">
|
||||||
|
<div class="theme-form-row">
|
||||||
|
<div class="theme-form-label">
|
||||||
|
<label for="userid">Votre équipe</label>
|
||||||
|
</div>
|
||||||
|
<select tabindex="1" required id="login" name="login" class="theme-form-input" autofocus>
|
||||||
|
{{ range $t := .Teams }} <option value="team{{ printf "%02d" $t.Id }}">{{ $t.Name }}</option>
|
||||||
|
{{ end }} </select>
|
||||||
|
</div>
|
||||||
|
<div class="theme-form-row">
|
||||||
|
<div class="theme-form-label">
|
||||||
|
<label for="password">Mot de passe</label>
|
||||||
|
</div>
|
||||||
|
<input tabindex="2" required id="password" name="password" type="password" class="theme-form-input" placeholder="mot de passe" {{ "{{" }} if .Invalid {{ "}}" }} autofocus {{ "{{" }} end {{ "}}" }}/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ "{{" }} if .Invalid {{ "}}" }}
|
||||||
|
<div id="login-error" class="dex-error-box">
|
||||||
|
Identifiants incorrects.
|
||||||
|
</div>
|
||||||
|
{{ "{{" }} end {{ "}}" }}
|
||||||
|
|
||||||
|
<button tabindex="3" id="submit-login" type="submit" class="dex-btn theme-btn--primary">C'est parti !</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
{{ "{{" }} if .BackLink {{ "}}" }}
|
||||||
|
<div class="theme-link-back">
|
||||||
|
<a class="dex-subtle-text" href="{{ "{{" }} .BackLink {{ "}}" }}">Sélectionner une autre méthode d'authentification.</a>
|
||||||
|
</div>
|
||||||
|
{{ "{{" }} end {{ "}}" }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{ "{{" }} template "footer.html" . {{ "}}" }}
|
||||||
|
`
|
||||||
|
|
||||||
|
type dexConfigClient struct {
|
||||||
|
Id string
|
||||||
|
Name string
|
||||||
|
RedirectURIs []string
|
||||||
|
Secret string
|
||||||
|
}
|
||||||
|
|
||||||
|
type dexConfig struct {
|
||||||
|
Clients []dexConfigClient
|
||||||
|
Teams []fic.Team
|
||||||
|
}
|
||||||
|
|
||||||
|
func genDexConfig() ([]byte, error) {
|
||||||
|
if teams, err := fic.GetTeams(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else if OidcSecret == "" {
|
||||||
|
return nil, fmt.Errorf("Unable to generate dex configuration: OIDC Secret not defined. Please define FICOIDC_SECRET in your environment.")
|
||||||
|
} else {
|
||||||
|
b := bytes.NewBufferString("")
|
||||||
|
|
||||||
|
if dexTmpl, err := template.New("dexcfg").Parse(dexcfgtpl); err != nil {
|
||||||
|
return nil, fmt.Errorf("Cannot create template: %w", err)
|
||||||
|
} else if err = dexTmpl.Execute(b, dexConfig{
|
||||||
|
Clients: []dexConfigClient{
|
||||||
|
dexConfigClient{
|
||||||
|
Id: "epita-challenge",
|
||||||
|
Name: "Challenge Forensic",
|
||||||
|
RedirectURIs: []string{"https://fic.srs.epita.fr/challenge_access/auth"},
|
||||||
|
Secret: OidcSecret,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Teams: teams,
|
||||||
|
}); err != nil {
|
||||||
|
return nil, fmt.Errorf("An error occurs during template execution: %w", err)
|
||||||
|
} else {
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func genDexPasswordTpl() ([]byte, error) {
|
||||||
|
if teams, err := fic.GetTeams(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
} else {
|
||||||
|
b := bytes.NewBufferString("")
|
||||||
|
|
||||||
|
if dexTmpl, err := template.New("dexpasswd").Parse(dexpasswdtpl); err != nil {
|
||||||
|
return nil, fmt.Errorf("Cannot create template: %w", err)
|
||||||
|
} else if err = dexTmpl.Execute(b, dexConfig{
|
||||||
|
Teams: teams,
|
||||||
|
}); err != nil {
|
||||||
|
return nil, fmt.Errorf("An error occurs during template execution: %w", err)
|
||||||
|
} else {
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -171,6 +171,10 @@ func updateTeam(team fic.Team, body []byte) (interface{}, error) {
|
||||||
|
|
||||||
ut.Id = team.Id
|
ut.Id = team.Id
|
||||||
|
|
||||||
|
if *ut.Password == "" {
|
||||||
|
ut.Password = nil
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := ut.Update(); err != nil {
|
if _, err := ut.Update(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,6 +71,9 @@ func main() {
|
||||||
baseURL := "/"
|
baseURL := "/"
|
||||||
|
|
||||||
// Read paremeters from environment
|
// Read paremeters from environment
|
||||||
|
if v, exists := os.LookupEnv("FICOIDC_SECRET"); exists {
|
||||||
|
api.OidcSecret = v
|
||||||
|
}
|
||||||
if v, exists := os.LookupEnv("FICCA_PASS"); exists {
|
if v, exists := os.LookupEnv("FICCA_PASS"); exists {
|
||||||
pki.SetCAPassword(v)
|
pki.SetCAPassword(v)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -5,38 +5,12 @@ import (
|
||||||
"crypto/elliptic"
|
"crypto/elliptic"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var PKIDir string
|
var PKIDir string
|
||||||
|
|
||||||
func GeneratePassword() (password string, err error) {
|
|
||||||
// This will make a 12 chars long password
|
|
||||||
b := make([]byte, 9)
|
|
||||||
|
|
||||||
if _, err = rand.Read(b); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
password = base64.StdEncoding.EncodeToString(b)
|
|
||||||
|
|
||||||
// Avoid hard to read characters
|
|
||||||
for _, i := range [][2]string{
|
|
||||||
{"v", "*"}, {"u", "("},
|
|
||||||
{"l", "%"}, {"1", "?"},
|
|
||||||
{"o", "@"}, {"O", "!"}, {"0", ">"},
|
|
||||||
// This one is to avoid problem with openssl
|
|
||||||
{"/", "^"},
|
|
||||||
} {
|
|
||||||
password = strings.Replace(password, i[0], i[1], -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func GeneratePrivKey() (pub *ecdsa.PublicKey, priv *ecdsa.PrivateKey, err error) {
|
func GeneratePrivKey() (pub *ecdsa.PublicKey, priv *ecdsa.PrivateKey, err error) {
|
||||||
if priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader); err == nil {
|
if priv, err = ecdsa.GenerateKey(elliptic.P384(), rand.Reader); err == nil {
|
||||||
pub = &priv.PublicKey
|
pub = &priv.PublicKey
|
||||||
|
|
|
@ -1961,6 +1961,17 @@ angular.module("FICApp")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.genDexCfg = function() {
|
||||||
|
$http.post("api/dex.yaml").then(function() {
|
||||||
|
$http.post("api/dex-password.tpl").then(function() {
|
||||||
|
$scope.addToast('success', 'Dex config refreshed.', "Don't forget to reload/reboot frontend host.");
|
||||||
|
}, function(response) {
|
||||||
|
$scope.addToast('danger', 'An error occurs when generating dex password tpl:', response.data.errmsg);
|
||||||
|
});
|
||||||
|
}, function(response) {
|
||||||
|
$scope.addToast('danger', 'An error occurs when generating dex config:', response.data.errmsg);
|
||||||
|
});
|
||||||
|
}
|
||||||
$scope.desactiveTeams = function() {
|
$scope.desactiveTeams = function() {
|
||||||
$http.post("api/disableinactiveteams").then(function() {
|
$http.post("api/disableinactiveteams").then(function() {
|
||||||
$scope.teams = Team.query();
|
$scope.teams = Team.query();
|
||||||
|
@ -1993,7 +2004,7 @@ angular.module("FICApp")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.controller("TeamController", function($scope, $rootScope, $location, Team, TeamMember, $routeParams) {
|
.controller("TeamController", function($scope, $rootScope, $location, Team, TeamMember, $routeParams, $http) {
|
||||||
if ($scope.team && $scope.team.id)
|
if ($scope.team && $scope.team.id)
|
||||||
$routeParams.teamId = $scope.team.id;
|
$routeParams.teamId = $scope.team.id;
|
||||||
$scope.team = Team.get({ teamId: $routeParams.teamId });
|
$scope.team = Team.get({ teamId: $routeParams.teamId });
|
||||||
|
@ -2010,6 +2021,14 @@ angular.module("FICApp")
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$scope.resetPasswd = function(team) {
|
||||||
|
$http({
|
||||||
|
url: "api/password",
|
||||||
|
method: "POST"
|
||||||
|
}).then(function(response) {
|
||||||
|
team.password = response.data.password;
|
||||||
|
});
|
||||||
|
}
|
||||||
$scope.deleteTeam = function() {
|
$scope.deleteTeam = function() {
|
||||||
backName = this.team.name;
|
backName = this.team.name;
|
||||||
this.team.$remove(function() { $scope.addToast('success', 'Team ' + backName + ' successfully removed.'); $location.url("/teams/"); $rootScope.staticFilesNeedUpdate++; },
|
this.team.$remove(function() { $scope.addToast('success', 'Team ' + backName + ' successfully removed.'); $location.url("/teams/"); $rootScope.staticFilesNeedUpdate++; },
|
||||||
|
|
|
@ -34,6 +34,19 @@
|
||||||
<input type="color" class="form-control form-control-sm" id="{{ field }}{{ member.id }}" ng-model="team[field]" ng-if="field == 'color'" color>
|
<input type="color" class="form-control form-control-sm" id="{{ field }}{{ member.id }}" ng-model="team[field]" ng-if="field == 'color'" color>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row" ng-if="team.id">
|
||||||
|
<label for="passwd" class="col-sm-2 col-form-label-sm">Mot de passe</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" class="form-control form-control-sm" id="passwd" ng-model="team.password">
|
||||||
|
<div class="input-group-append">
|
||||||
|
<button class="btn btn-sm btn-outline-danger" type="button" ng-click="resetPasswd(team)">
|
||||||
|
<span class="glyphicon glyphicon-random" aria-hidden="true"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="text-right" ng-show="team.id">
|
<div class="text-right" ng-show="team.id">
|
||||||
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-save" aria-hidden="true"></span> Save</button>
|
<button type="submit" class="btn btn-success"><span class="glyphicon glyphicon-save" aria-hidden="true"></span> Save</button>
|
||||||
<button type="button" class="btn btn-danger" ng-click="deleteTeam()"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span> Delete</button>
|
<button type="button" class="btn btn-danger" ng-click="deleteTeam()"><span class="glyphicon glyphicon-remove" aria-hidden="true"></span> Delete</button>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
<button type="button" ng-click="show('print')" class="float-right btn btn-sm btn-secondary mr-2"><span class="glyphicon glyphicon-print" aria-hidden="true"></span> Imprimer les équipes</button>
|
<button type="button" ng-click="show('print')" class="float-right btn btn-sm btn-secondary mr-2"><span class="glyphicon glyphicon-print" aria-hidden="true"></span> Imprimer les équipes</button>
|
||||||
<button type="button" ng-click="show('export')" class="float-right btn btn-sm btn-secondary mr-2"><span class="glyphicon glyphicon-export" aria-hidden="true"></span> Statistiques générales</button>
|
<button type="button" ng-click="show('export')" class="float-right btn btn-sm btn-secondary mr-2"><span class="glyphicon glyphicon-export" aria-hidden="true"></span> Statistiques générales</button>
|
||||||
<button type="button" ng-click="desactiveTeams()" class="float-right btn btn-sm btn-danger mr-2" title="Cliquer pour marquer les équipes sans certificat comme inactives (et ainsi éviter que ses fichiers ne soient générés)"><span class="glyphicon glyphicon-leaf" aria-hidden="true"></span> Désactiver les équipes inactives</button>
|
<button type="button" ng-click="desactiveTeams()" class="float-right btn btn-sm btn-danger mr-2" title="Cliquer pour marquer les équipes sans certificat comme inactives (et ainsi éviter que ses fichiers ne soient générés)"><span class="glyphicon glyphicon-leaf" aria-hidden="true"></span> Désactiver les équipes inactives</button>
|
||||||
|
<button type="button" ng-click="genDexCfg()" class="float-right btn btn-sm btn-success mr-2"><span class="glyphicon glyphicon-refresh" aria-hidden="true"></span> DexIdP</button>
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<p><input type="search" class="form-control" placeholder="Search" ng-model="query" ng-keypress="validateSearch($event)" autofocus></p>
|
<p><input type="search" class="form-control" placeholder="Search" ng-model="query" ng-keypress="validateSearch($event)" autofocus></p>
|
||||||
|
|
|
@ -99,7 +99,8 @@ CREATE TABLE IF NOT EXISTS teams(
|
||||||
name VARCHAR(255) NOT NULL,
|
name VARCHAR(255) NOT NULL,
|
||||||
color INTEGER NOT NULL,
|
color INTEGER NOT NULL,
|
||||||
active BOOLEAN NOT NULL DEFAULT 1,
|
active BOOLEAN NOT NULL DEFAULT 1,
|
||||||
external_id VARCHAR(255) NOT NULL
|
external_id VARCHAR(255) NOT NULL,
|
||||||
|
password VARCHAR(255) NULL
|
||||||
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
) DEFAULT CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
|
||||||
`); err != nil {
|
`); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package fic
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GeneratePassword() (password string, err error) {
|
||||||
|
// This will make a 12 chars long password
|
||||||
|
b := make([]byte, 9)
|
||||||
|
|
||||||
|
if _, err = rand.Read(b); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
password = base64.StdEncoding.EncodeToString(b)
|
||||||
|
|
||||||
|
// Avoid hard to read characters
|
||||||
|
for _, i := range [][2]string{
|
||||||
|
{"v", "*"}, {"u", "("},
|
||||||
|
{"l", "%"}, {"1", "?"},
|
||||||
|
{"o", "@"}, {"O", "!"}, {"0", ">"},
|
||||||
|
// This one is to avoid problem with openssl
|
||||||
|
{"/", "^"},
|
||||||
|
} {
|
||||||
|
password = strings.Replace(password, i[0], i[1], -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"math"
|
"math"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UnlockedChallengeDepth is the number of challenges to unlock ahead (0: only the next one, -1: all)
|
// UnlockedChallengeDepth is the number of challenges to unlock ahead (0: only the next one, -1: all)
|
||||||
|
@ -14,15 +16,16 @@ var WChoiceCoefficient = 1.0
|
||||||
|
|
||||||
// Team represents a group of players, come to solve our challenges.
|
// Team represents a group of players, come to solve our challenges.
|
||||||
type Team struct {
|
type Team struct {
|
||||||
Id int64 `json:"id"`
|
Id int64 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Color uint32 `json:"color"`
|
Color uint32 `json:"color"`
|
||||||
Active bool `json:"active"`
|
Active bool `json:"active"`
|
||||||
ExternalId string `json:"external_id"`
|
ExternalId string `json:"external_id"`
|
||||||
|
Password *string `json:"password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func getTeams(filter string) ([]Team, error) {
|
func getTeams(filter string) ([]Team, error) {
|
||||||
if rows, err := DBQuery("SELECT id_team, name, color, active, external_id FROM teams " + filter); err != nil {
|
if rows, err := DBQuery("SELECT id_team, name, color, active, external_id, password FROM teams " + filter); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
defer rows.Close()
|
defer rows.Close()
|
||||||
|
@ -30,7 +33,7 @@ func getTeams(filter string) ([]Team, error) {
|
||||||
var teams = make([]Team, 0)
|
var teams = make([]Team, 0)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
var t Team
|
var t Team
|
||||||
if err := rows.Scan(&t.Id, &t.Name, &t.Color, &t.Active, &t.ExternalId); err != nil {
|
if err := rows.Scan(&t.Id, &t.Name, &t.Color, &t.Active, &t.ExternalId, &t.Password); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
teams = append(teams, t)
|
teams = append(teams, t)
|
||||||
|
@ -56,7 +59,7 @@ func GetActiveTeams() ([]Team, error) {
|
||||||
// GetTeam retrieves a Team from its identifier.
|
// GetTeam retrieves a Team from its identifier.
|
||||||
func GetTeam(id int64) (Team, error) {
|
func GetTeam(id int64) (Team, error) {
|
||||||
var t Team
|
var t Team
|
||||||
if err := DBQueryRow("SELECT id_team, name, color, active, external_id FROM teams WHERE id_team = ?", id).Scan(&t.Id, &t.Name, &t.Color, &t.Active, &t.ExternalId); err != nil {
|
if err := DBQueryRow("SELECT id_team, name, color, active, external_id, password FROM teams WHERE id_team = ?", id).Scan(&t.Id, &t.Name, &t.Color, &t.Active, &t.ExternalId, &t.Password); err != nil {
|
||||||
return t, err
|
return t, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +69,7 @@ func GetTeam(id int64) (Team, error) {
|
||||||
// GetTeamBySerial retrieves a Team from one of its associated certificates.
|
// GetTeamBySerial retrieves a Team from one of its associated certificates.
|
||||||
func GetTeamBySerial(serial int64) (Team, error) {
|
func GetTeamBySerial(serial int64) (Team, error) {
|
||||||
var t Team
|
var t Team
|
||||||
if err := DBQueryRow("SELECT T.id_team, T.name, T.color, T.active, T.external_id FROM certificates C INNER JOIN teams T ON T.id_team = C.id_team WHERE id_cert = ?", serial).Scan(&t.Id, &t.Name, &t.Color, &t.Active, &t.ExternalId); err != nil {
|
if err := DBQueryRow("SELECT T.id_team, T.name, T.color, T.active, T.external_id, T.password FROM certificates C INNER JOIN teams T ON T.id_team = C.id_team WHERE id_cert = ?", serial).Scan(&t.Id, &t.Name, &t.Color, &t.Active, &t.ExternalId, &t.Password); err != nil {
|
||||||
return t, err
|
return t, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,13 +83,13 @@ func CreateTeam(name string, color uint32, externalId string) (Team, error) {
|
||||||
} else if tid, err := res.LastInsertId(); err != nil {
|
} else if tid, err := res.LastInsertId(); err != nil {
|
||||||
return Team{}, err
|
return Team{}, err
|
||||||
} else {
|
} else {
|
||||||
return Team{tid, name, color, true, ""}, nil
|
return Team{tid, name, color, true, "", nil}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update applies modifications back to the database.
|
// Update applies modifications back to the database.
|
||||||
func (t Team) Update() (int64, error) {
|
func (t Team) Update() (int64, error) {
|
||||||
if res, err := DBExec("UPDATE teams SET name = ?, color = ?, active = ?, external_id = ? WHERE id_team = ?", t.Name, t.Color, t.Active, t.ExternalId, t.Id); err != nil {
|
if res, err := DBExec("UPDATE teams SET name = ?, color = ?, active = ?, external_id = ?, password = ? WHERE id_team = ?", t.Name, t.Color, t.Active, t.ExternalId, t.Password, t.Id); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
} else if nb, err := res.RowsAffected(); err != nil {
|
} else if nb, err := res.RowsAffected(); err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
|
@ -310,3 +313,18 @@ func (t Team) HasPartiallySolved(f Flag) (tm *time.Time) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HashedPassword compute a bcrypt version of the team's password.
|
||||||
|
func (t Team) HashedPassword() (string, error) {
|
||||||
|
if t.Password == nil {
|
||||||
|
if passwd, err := GeneratePassword(); err != nil {
|
||||||
|
return "", err
|
||||||
|
} else {
|
||||||
|
h, err := bcrypt.GenerateFromPassword([]byte(passwd), bcrypt.DefaultCost)
|
||||||
|
return string(h), err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h, err := bcrypt.GenerateFromPassword([]byte(*t.Password), bcrypt.DefaultCost)
|
||||||
|
return string(h), err
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue