Implement per-project webhook secret

This commit is contained in:
simon987 2019-02-24 15:30:38 -05:00
parent c736cc3d98
commit 397805f915
22 changed files with 378 additions and 59 deletions

View File

@ -16,13 +16,6 @@ import (
func (api *WebAPI) ReceiveGitWebHook(r *Request) { func (api *WebAPI) ReceiveGitWebHook(r *Request) {
if !signatureValid(r) {
logrus.Error("WebHook signature does not match!")
r.Ctx.SetStatusCode(403)
_, _ = fmt.Fprintf(r.Ctx, "Signature does not match")
return
}
payload := &GitPayload{} payload := &GitPayload{}
err := json.Unmarshal(r.Ctx.Request.Body(), payload) err := json.Unmarshal(r.Ctx.Request.Body(), payload)
if err != nil { if err != nil {
@ -35,11 +28,27 @@ func (api *WebAPI) ReceiveGitWebHook(r *Request) {
}).Info("Received git WebHook") }).Info("Received git WebHook")
if !isProductionBranch(payload) { if !isProductionBranch(payload) {
r.Ctx.SetStatusCode(400)
return return
} }
project := api.getAssociatedProject(payload) project := api.getAssociatedProject(payload)
if project == nil { if project == nil {
r.Ctx.SetStatusCode(400)
return
}
signature, err := api.Database.GetWebhookSecret(project.Id)
if err != nil {
_, _ = fmt.Fprintf(r.Ctx, err.Error())
r.Ctx.SetStatusCode(400)
return
}
if !signatureValid(r, signature) {
logrus.Error("WebHook signature does not match!")
r.Ctx.SetStatusCode(403)
_, _ = fmt.Fprintf(r.Ctx, "Signature does not match")
return return
} }
@ -50,7 +59,7 @@ func (api *WebAPI) ReceiveGitWebHook(r *Request) {
handleErr(err, r) handleErr(err, r)
} }
func signatureValid(r *Request) (matches bool) { func signatureValid(r *Request, webhookSignature string) (matches bool) {
signature := parseSignatureFromRequest(r.Ctx) signature := parseSignatureFromRequest(r.Ctx)
@ -60,7 +69,7 @@ func signatureValid(r *Request) (matches bool) {
body := r.Ctx.PostBody() body := r.Ctx.PostBody()
mac := hmac.New(getHashFuncFromConfig(), config.Cfg.WebHookSecret) mac := hmac.New(getHashFuncFromConfig(), []byte(webhookSignature))
mac.Write(body) mac.Write(body)
expectedMac := hex.EncodeToString(mac.Sum(nil)) expectedMac := hex.EncodeToString(mac.Sum(nil))

View File

@ -93,6 +93,8 @@ func New() *WebAPI {
api.router.POST("/project/reject_request/:id/:wid", LogRequestMiddleware(api.RejectAccessRequest)) api.router.POST("/project/reject_request/:id/:wid", LogRequestMiddleware(api.RejectAccessRequest))
api.router.GET("/project/secret/:id", LogRequestMiddleware(api.GetSecret)) api.router.GET("/project/secret/:id", LogRequestMiddleware(api.GetSecret))
api.router.POST("/project/secret/:id", LogRequestMiddleware(api.SetSecret)) api.router.POST("/project/secret/:id", LogRequestMiddleware(api.SetSecret))
api.router.GET("/project/webhook_secret/:id", LogRequestMiddleware(api.GetWebhookSecret))
api.router.POST("/project/webhook_secret/:id", LogRequestMiddleware(api.SetWebhookSecret))
api.router.POST("/task/submit", LogRequestMiddleware(api.SubmitTask)) api.router.POST("/task/submit", LogRequestMiddleware(api.SubmitTask))
api.router.GET("/task/get/:project", LogRequestMiddleware(api.GetTaskFromProject)) api.router.GET("/task/get/:project", LogRequestMiddleware(api.GetTaskFromProject))

View File

@ -292,3 +292,10 @@ type SetSecretRequest struct {
type GetSecretResponse struct { type GetSecretResponse struct {
Secret string `json:"secret"` Secret string `json:"secret"`
} }
type SetWebhookSecretRequest struct {
WebhookSecret string `json:"webhook_secret"`
}
type GetWebhookSecretResponse struct {
WebhookSecret string `json:"webhook_secret"`
}

View File

@ -3,6 +3,7 @@ package api
import ( import (
"encoding/json" "encoding/json"
"github.com/Sirupsen/logrus" "github.com/Sirupsen/logrus"
"github.com/google/uuid"
"github.com/simon987/task_tracker/storage" "github.com/simon987/task_tracker/storage"
"strconv" "strconv"
) )
@ -97,7 +98,9 @@ func (api *WebAPI) CreateProject(r *Request) {
return return
} }
id, err := api.Database.SaveProject(project) webhookSecret := makeWebhookSecret()
id, err := api.Database.SaveProject(project, webhookSecret)
if err != nil { if err != nil {
r.Json(JsonResponse{ r.Json(JsonResponse{
Ok: false, Ok: false,
@ -107,7 +110,7 @@ func (api *WebAPI) CreateProject(r *Request) {
} }
api.Database.SetManagerRoleOn(manager.(*storage.Manager).Id, id, api.Database.SetManagerRoleOn(manager.(*storage.Manager).Id, id,
storage.ROLE_MANAGE_ACCESS|storage.ROLE_READ|storage.ROLE_EDIT) storage.RoleManageAccess|storage.RoleRead|storage.RoleEdit|storage.RoleSecret)
r.OkJson(JsonResponse{ r.OkJson(JsonResponse{
Ok: true, Ok: true,
Content: CreateProjectResponse{ Content: CreateProjectResponse{
@ -119,6 +122,10 @@ func (api *WebAPI) CreateProject(r *Request) {
}).Debug("Created project") }).Debug("Created project")
} }
func makeWebhookSecret() string {
return uuid.New().String()
}
func (api *WebAPI) UpdateProject(r *Request) { func (api *WebAPI) UpdateProject(r *Request) {
id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64) id, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
@ -163,7 +170,7 @@ func (api *WebAPI) UpdateProject(r *Request) {
sess := api.Session.StartFasthttp(r.Ctx) sess := api.Session.StartFasthttp(r.Ctx)
manager := sess.Get("manager") manager := sess.Get("manager")
if !isActionOnProjectAuthorized(project.Id, manager, storage.ROLE_EDIT, api.Database) { if !isActionOnProjectAuthorized(project.Id, manager, storage.RoleEdit, api.Database) {
r.Json(JsonResponse{ r.Json(JsonResponse{
Ok: false, Ok: false,
Message: "Unauthorized", Message: "Unauthorized",
@ -238,7 +245,7 @@ func isProjectReadAuthorized(project *storage.Project, manager interface{}, db *
return true return true
} }
role := db.GetManagerRoleOn(manager.(*storage.Manager), project.Id) role := db.GetManagerRoleOn(manager.(*storage.Manager), project.Id)
if role&storage.ROLE_READ == 1 { if role&storage.RoleRead == 1 {
return true return true
} }
@ -302,7 +309,7 @@ func (api *WebAPI) GetWorkerAccessListForProject(r *Request) {
return return
} }
if !isActionOnProjectAuthorized(id, manager, storage.ROLE_MANAGE_ACCESS, api.Database) { if !isActionOnProjectAuthorized(id, manager, storage.RoleManageAccess, api.Database) {
r.Json(JsonResponse{ r.Json(JsonResponse{
Ok: false, Ok: false,
Message: "Unauthorized", Message: "Unauthorized",
@ -391,7 +398,7 @@ func (api *WebAPI) AcceptAccessRequest(r *Request) {
sess := api.Session.StartFasthttp(r.Ctx) sess := api.Session.StartFasthttp(r.Ctx)
manager := sess.Get("manager") manager := sess.Get("manager")
if !isActionOnProjectAuthorized(pid, manager, storage.ROLE_MANAGE_ACCESS, api.Database) { if !isActionOnProjectAuthorized(pid, manager, storage.RoleManageAccess, api.Database) {
r.Json(JsonResponse{ r.Json(JsonResponse{
Message: "Unauthorized", Message: "Unauthorized",
Ok: false, Ok: false,
@ -471,7 +478,7 @@ func (api *WebAPI) SetManagerRoleOnProject(r *Request) {
sess := api.Session.StartFasthttp(r.Ctx) sess := api.Session.StartFasthttp(r.Ctx)
manager := sess.Get("manager") manager := sess.Get("manager")
if !isActionOnProjectAuthorized(pid, manager, storage.ROLE_MANAGE_ACCESS, api.Database) { if !isActionOnProjectAuthorized(pid, manager, storage.RoleManageAccess, api.Database) {
r.Json(JsonResponse{ r.Json(JsonResponse{
Message: "Unauthorized", Message: "Unauthorized",
Ok: false, Ok: false,
@ -500,7 +507,7 @@ func (api *WebAPI) SetSecret(r *Request) {
sess := api.Session.StartFasthttp(r.Ctx) sess := api.Session.StartFasthttp(r.Ctx)
manager := sess.Get("manager") manager := sess.Get("manager")
if !isActionOnProjectAuthorized(pid, manager, storage.ROLE_EDIT, api.Database) { if !isActionOnProjectAuthorized(pid, manager, storage.RoleSecret, api.Database) {
r.Json(JsonResponse{ r.Json(JsonResponse{
Ok: false, Ok: false,
Message: "Unauthorized", Message: "Unauthorized",
@ -560,7 +567,7 @@ func (api *WebAPI) GetSecret(r *Request) {
sess := api.Session.StartFasthttp(r.Ctx) sess := api.Session.StartFasthttp(r.Ctx)
manager := sess.Get("manager") manager := sess.Get("manager")
if !isActionOnProjectAuthorized(pid, manager, storage.ROLE_EDIT, api.Database) { if !isActionOnProjectAuthorized(pid, manager, storage.RoleSecret, api.Database) {
r.Json(JsonResponse{ r.Json(JsonResponse{
Ok: false, Ok: false,
Message: "Unauthorized", Message: "Unauthorized",
@ -576,3 +583,79 @@ func (api *WebAPI) GetSecret(r *Request) {
}, },
}) })
} }
func (api *WebAPI) GetWebhookSecret(r *Request) {
pid, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
if err != nil || pid <= 0 {
r.Json(JsonResponse{
Ok: false,
Message: "Invalid project id",
}, 400)
return
}
sess := api.Session.StartFasthttp(r.Ctx)
manager := sess.Get("manager")
if !isActionOnProjectAuthorized(pid, manager, storage.RoleSecret, api.Database) {
r.Json(JsonResponse{
Ok: false,
Message: "Unauthorized",
}, 403)
return
}
secret, err := api.Database.GetWebhookSecret(pid)
r.OkJson(JsonResponse{
Ok: true,
Content: GetWebhookSecretResponse{
WebhookSecret: secret,
},
})
}
func (api *WebAPI) SetWebhookSecret(r *Request) {
pid, err := strconv.ParseInt(r.Ctx.UserValue("id").(string), 10, 64)
if err != nil || pid <= 0 {
r.Json(JsonResponse{
Ok: false,
Message: "Invalid project id",
}, 400)
return
}
req := &SetWebhookSecretRequest{}
err = json.Unmarshal(r.Ctx.Request.Body(), req)
if err != nil {
r.Json(JsonResponse{
Ok: false,
Message: "Could not parse request",
}, 400)
return
}
sess := api.Session.StartFasthttp(r.Ctx)
manager := sess.Get("manager")
if !isActionOnProjectAuthorized(pid, manager, storage.RoleSecret, api.Database) {
r.Json(JsonResponse{
Ok: false,
Message: "Unauthorized",
}, 403)
return
}
err = api.Database.SetWebhookSecret(pid, req.WebhookSecret)
if err == nil {
r.OkJson(JsonResponse{
Ok: true,
})
} else {
r.OkJson(JsonResponse{
Ok: false,
Message: err.Error(),
})
}
}

View File

@ -7,7 +7,6 @@ database:
log_levels: ["error", "info", "warn"] log_levels: ["error", "info", "warn"]
git: git:
webhook_secret: "very_secret_secret"
# Github: sha1, Gogs: sha256 # Github: sha1, Gogs: sha256
webhook_hash: "sha256" webhook_hash: "sha256"
# Github: 'X-Hub-Signature', Gogs: 'X-Gogs-Signature' # Github: 'X-Hub-Signature', Gogs: 'X-Gogs-Signature'

View File

@ -11,10 +11,11 @@ import (
type ManagerRole int type ManagerRole int
const ( const (
ROLE_NONE ManagerRole = 0 RoleNone ManagerRole = 0
ROLE_READ ManagerRole = 1 RoleRead ManagerRole = 1
ROLE_EDIT ManagerRole = 2 RoleEdit ManagerRole = 2
ROLE_MANAGE_ACCESS ManagerRole = 4 RoleManageAccess ManagerRole = 4
RoleSecret ManagerRole = 8
) )
type Manager struct { type Manager struct {
@ -142,7 +143,7 @@ func (database *Database) GetManagerRoleOn(manager *Manager, projectId int64) Ma
var role ManagerRole var role ManagerRole
err := row.Scan(&role) err := row.Scan(&role)
if err != nil { if err != nil {
return ROLE_NONE return RoleNone
} }
return role return role

View File

@ -25,14 +25,14 @@ type AssignedTasks struct {
TaskCount int64 `json:"task_count"` TaskCount int64 `json:"task_count"`
} }
func (database *Database) SaveProject(project *Project) (int64, error) { func (database *Database) SaveProject(project *Project, webhookSecret string) (int64, error) {
db := database.getDB() db := database.getDB()
row := db.QueryRow(`INSERT INTO project (name, git_repo, clone_url, version, priority, row := db.QueryRow(`INSERT INTO project (name, git_repo, clone_url, version, priority,
motd, public, hidden, chain, paused) motd, public, hidden, chain, paused, webhook_secret)
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,NULLIF($9, 0),$10) RETURNING id`, VALUES ($1,$2,$3,$4,$5,$6,$7,$8,NULLIF($9, 0),$10,$11) RETURNING id`,
project.Name, project.GitRepo, project.CloneUrl, project.Version, project.Priority, project.Motd, project.Name, project.GitRepo, project.CloneUrl, project.Version, project.Priority, project.Motd,
project.Public, project.Hidden, project.Chain, project.Paused) project.Public, project.Hidden, project.Chain, project.Paused, webhookSecret)
var id int64 var id int64
err := row.Scan(&id) err := row.Scan(&id)
@ -225,3 +225,23 @@ func (database *Database) SetSecret(pid int64, secret string) {
"project": pid, "project": pid,
}).Info("Set secret") }).Info("Set secret")
} }
func (database *Database) GetWebhookSecret(pid int64) (secret string, err error) {
db := database.getDB()
row := db.QueryRow(`SELECT webhook_secret FROM project WHERE id=$1`, pid)
err = row.Scan(&secret)
return
}
func (database *Database) SetWebhookSecret(pid int64, secret string) (err error) {
db := database.getDB()
res, err := db.Exec(`UPDATE project SET webhook_secret=$1 WHERE id=$2`, secret, pid)
handleErr(err)
rowsAffected, _ := res.RowsAffected()
logrus.WithFields(logrus.Fields{
"project": pid,
"rowsAffected": rowsAffected,
}).Trace("Update webhook secret")
return
}

View File

@ -3,6 +3,7 @@ package test
import ( import (
"bytes" "bytes"
"encoding/json" "encoding/json"
"fmt"
"github.com/simon987/task_tracker/api" "github.com/simon987/task_tracker/api"
"github.com/simon987/task_tracker/config" "github.com/simon987/task_tracker/config"
"golang.org/x/net/publicsuffix" "golang.org/x/net/publicsuffix"
@ -177,3 +178,15 @@ func getSessionCtx(username string, password string, admin bool) *http.Client {
return client return client
} }
func setRoleOnProject(req api.SetManagerRoleOnProjectRequest, pid int64, s *http.Client) (ar api.JsonResponse) {
r := Post(fmt.Sprintf("/manager/set_role_for_project/%d", pid), req, nil, s)
UnmarshalResponse(r, &ar)
return
}
func getAccountDetails(s *http.Client) (ar AccountAR) {
r := Get("/account", nil, s)
UnmarshalResponse(r, &ar)
return
}

View File

@ -15,7 +15,7 @@ func TestWebHookNoSignature(t *testing.T) {
r := Post("/git/receivehook", api.GitPayload{}, nil, nil) r := Post("/git/receivehook", api.GitPayload{}, nil, nil)
if r.StatusCode != 403 { if r.StatusCode == 200 {
t.Error() t.Error()
} }
} }
@ -28,7 +28,7 @@ func TestWebHookInvalidSignature(t *testing.T) {
client := http.Client{} client := http.Client{}
r, _ := client.Do(req) r, _ := client.Do(req)
if r.StatusCode != 403 { if r.StatusCode == 200 {
t.Error() t.Error()
} }
} }
@ -41,10 +41,12 @@ func TestWebHookDontUpdateVersion(t *testing.T) {
GitRepo: "username/not_this_one", GitRepo: "username/not_this_one",
}).Content }).Content
webhookSecret := getWebhookSecret(resp.Id, testAdminCtx).Content.WebhookSecret
body := []byte(`{"ref": "refs/heads/master", "after": "new", "repository": {"full_name": "username/repo_name"}}`) body := []byte(`{"ref": "refs/heads/master", "after": "new", "repository": {"full_name": "username/repo_name"}}`)
bodyReader := bytes.NewReader(body) bodyReader := bytes.NewReader(body)
mac := hmac.New(crypto.SHA1.New, config.Cfg.WebHookSecret) mac := hmac.New(crypto.SHA1.New, []byte(webhookSecret))
mac.Write(body) mac.Write(body)
signature := hex.EncodeToString(mac.Sum(nil)) signature := hex.EncodeToString(mac.Sum(nil))
signature = "sha1=" + signature signature = "sha1=" + signature
@ -53,11 +55,7 @@ func TestWebHookDontUpdateVersion(t *testing.T) {
req.Header.Add("X-Hub-Signature", signature) req.Header.Add("X-Hub-Signature", signature)
client := http.Client{} client := http.Client{}
r, _ := client.Do(req) _, _ = client.Do(req)
if r.StatusCode != 200 {
t.Error()
}
getResp := getProjectAsAdmin(resp.Id).Content getResp := getProjectAsAdmin(resp.Id).Content
@ -76,7 +74,9 @@ func TestWebHookUpdateVersion(t *testing.T) {
body := []byte(`{"ref": "refs/heads/master", "after": "new", "repository": {"full_name": "username/repo_name"}}`) body := []byte(`{"ref": "refs/heads/master", "after": "new", "repository": {"full_name": "username/repo_name"}}`)
bodyReader := bytes.NewReader(body) bodyReader := bytes.NewReader(body)
mac := hmac.New(crypto.SHA1.New, config.Cfg.WebHookSecret) webhookSecret := getWebhookSecret(resp.Id, testAdminCtx).Content.WebhookSecret
mac := hmac.New(crypto.SHA1.New, []byte(webhookSecret))
mac.Write(body) mac.Write(body)
signature := hex.EncodeToString(mac.Sum(nil)) signature := hex.EncodeToString(mac.Sum(nil))
signature = "sha1=" + signature signature = "sha1=" + signature

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/simon987/task_tracker/api" "github.com/simon987/task_tracker/api"
"github.com/simon987/task_tracker/storage"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"testing" "testing"
@ -465,6 +466,82 @@ func TestPausedProjectShouldNotDispatchTasks(t *testing.T) {
}, testProject, testAdminCtx) }, testProject, testAdminCtx)
} }
func TestGetWebhookSecret(t *testing.T) {
resp := getWebhookSecret(testProject, testAdminCtx)
if resp.Ok != true {
t.Error()
}
if len(resp.Content.WebhookSecret) <= 0 {
t.Error()
}
}
func TestSetWebhookSecret(t *testing.T) {
resp1 := setWebhookSecret(api.SetWebhookSecretRequest{
WebhookSecret: "new",
}, testProject, testAdminCtx)
if resp1.Ok != true {
t.Error()
}
resp := getWebhookSecret(testProject, testAdminCtx)
if resp.Ok != true {
t.Error()
}
if resp.Content.WebhookSecret != "new" {
t.Error()
}
}
func TestGetWebhookRequiresRole(t *testing.T) {
otherUser := getSessionCtx("testwebhookrole", "testwebhookrole", false)
otherUserId := getAccountDetails(otherUser).Content.Id
user := getSessionCtx("testwebhookroleu", "testwebhookroleu", false)
userId := getAccountDetails(user).Content.Id
resp := setRoleOnProject(api.SetManagerRoleOnProjectRequest{
Role: storage.RoleEdit | storage.RoleManageAccess | storage.RoleRead,
Manager: otherUserId,
}, testProject, testAdminCtx)
if resp.Ok != true {
t.Fail()
}
resp = setRoleOnProject(api.SetManagerRoleOnProjectRequest{
Role: storage.RoleSecret,
Manager: userId,
}, testProject, testAdminCtx)
if resp.Ok != true {
t.Fail()
}
rUser := setWebhookSecret(api.SetWebhookSecretRequest{
WebhookSecret: "test",
}, testProject, user)
rOther := setWebhookSecret(api.SetWebhookSecretRequest{
WebhookSecret: "test",
}, testProject, otherUser)
rGuest := setWebhookSecret(api.SetWebhookSecretRequest{
WebhookSecret: "test",
}, testProject, nil)
if rUser.Ok != true {
t.Error()
}
if rOther.Ok != false {
t.Error()
}
if rGuest.Ok != false {
t.Error()
}
}
func createProjectAsAdmin(req api.CreateProjectRequest) CreateProjectAR { func createProjectAsAdmin(req api.CreateProjectRequest) CreateProjectAR {
return createProject(req, testAdminCtx) return createProject(req, testAdminCtx)
} }
@ -502,3 +579,15 @@ func getProjectList(s *http.Client) (ar ProjectListAR) {
UnmarshalResponse(r, &ar) UnmarshalResponse(r, &ar)
return return
} }
func getWebhookSecret(pid int64, s *http.Client) (ar WebhookSecretAR) {
r := Get(fmt.Sprintf("/project/webhook_secret/%d", pid), nil, s)
UnmarshalResponse(r, &ar)
return
}
func setWebhookSecret(req api.SetWebhookSecretRequest, pid int64, s *http.Client) (ar api.JsonResponse) {
r := Post(fmt.Sprintf("/project/webhook_secret/%d", pid), req, nil, s)
UnmarshalResponse(r, &ar)
return
}

View File

@ -172,3 +172,19 @@ type ReleaseAR struct {
Updated bool `json:"updated"` Updated bool `json:"updated"`
} `json:"content"` } `json:"content"`
} }
type WebhookSecretAR struct {
Ok bool `json:"ok"`
Message string `json:"message"`
Content struct {
WebhookSecret string `json:"webhook_secret"`
} `json:"content"`
}
type AccountAR struct {
Ok bool `json:"ok"`
Message string `json:"message"`
Content struct {
*storage.Manager `json:"manager"`
} `json:"content"`
}

View File

@ -6,7 +6,6 @@ database:
log_levels: ["debug", "error", "trace", "info", "warn"] log_levels: ["debug", "error", "trace", "info", "warn"]
git: git:
webhook_secret: "very_secret_secret"
webhook_hash: "sha1" webhook_hash: "sha1"
webhook_sig_header: "X-Hub-Signature" webhook_sig_header: "X-Hub-Signature"

View File

@ -25,7 +25,8 @@ CREATE TABLE project
git_repo TEXT NOT NULL, git_repo TEXT NOT NULL,
version TEXT NOT NULL, version TEXT NOT NULL,
motd TEXT NOT NULL, motd TEXT NOT NULL,
secret TEXT NOT NULL DEFAULT '{}' secret TEXT NOT NULL DEFAULT '{}',
webhook_secret TEXT NOT NULL
); );
CREATE TABLE worker_access CREATE TABLE worker_access

View File

@ -106,4 +106,12 @@ export class ApiService {
return this.http.post(this.url + `/project/secret/${pid}`, {"secret": secret}) return this.http.post(this.url + `/project/secret/${pid}`, {"secret": secret})
} }
getWebhookSecret(pid: number) {
return this.http.get(this.url + `/project/webhook_secret/${pid}`,)
}
setWebhookSecret(pid: number, secret: string) {
return this.http.post(this.url + `/project/webhook_secret/${pid}`, {"webhook_secret": secret})
}
} }

View File

@ -51,4 +51,16 @@ export class ManagerRoleOnProject {
this.role &= ~4 this.role &= ~4
} }
} }
get secretRole(): boolean {
return (this.role & 8) != 0
}
set secretRole(role: boolean) {
if (role) {
this.role |= 8
} else {
this.role &= ~8
}
}
} }

View File

@ -366,13 +366,14 @@ export class ProjectDashboardComponent implements OnInit {
this.dialog.open(AreYouSureComponent, { this.dialog.open(AreYouSureComponent, {
width: '250px', width: '250px',
}).afterClosed().subscribe(result => { }).afterClosed().subscribe(result => {
if (result) {
this.project.paused = paused; this.project.paused = paused;
this.apiService.updateProject(this.project).subscribe(() => { this.apiService.updateProject(this.project).subscribe(() => {
this.translate.get("messenger.acknowledged").subscribe(t => this.translate.get("messenger.acknowledged").subscribe(t =>
this.messenger.show(t)) this.messenger.show(t))
}, error => {
this.translate.get("messenger.unauthorized").subscribe(t =>
this.messenger.show(t))
}) })
}
}); });
} }

View File

@ -33,6 +33,7 @@
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
*ngIf="authService.logged" *ngIf="authService.logged"
[routerLink]="'/project/' + project.id + '/secret'"> [routerLink]="'/project/' + project.id + '/secret'">
<mat-icon>security</mat-icon>
{{"project.secret" | translate}}</button> {{"project.secret" | translate}}</button>
</div> </div>
</mat-expansion-panel> </mat-expansion-panel>

View File

@ -55,6 +55,10 @@
(change)="onRoleChange(m)" (change)="onRoleChange(m)"
[disabled]="m.manager.id==auth.account.id" [disabled]="m.manager.id==auth.account.id"
>{{"perms.manage"|translate}}</mat-checkbox> >{{"perms.manage"|translate}}</mat-checkbox>
<mat-checkbox [(ngModel)]="m.secretRole"
(change)="onRoleChange(m)"
[disabled]="m.manager.id==auth.account.id"
>{{"perms.secret"|translate}}</mat-checkbox>
</mat-list-item> </mat-list-item>
</mat-list> </mat-list>
</mat-card-content> </mat-card-content>

View File

@ -1,22 +1,47 @@
<div class="container"> <div class="container">
<mat-card> <mat-card class="mat-elevation-z8">
<button mat-button [title]="'perms.refresh' | translate" style="float:right"
(click)="refresh()">
<mat-icon>refresh</mat-icon>
</button>
<mat-card-header> <mat-card-header>
<mat-card-title>{{"secret.title" | translate}}</mat-card-title> <mat-card-title>{{"secret.title" | translate}}</mat-card-title>
<mat-card-subtitle>{{"secret.subtitle" | translate}}</mat-card-subtitle> <mat-card-subtitle>{{"secret.subtitle" | translate}}</mat-card-subtitle>
</mat-card-header> </mat-card-header>
<mat-card-content>
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>{{"secret.secret" | translate}}</mat-label> <mat-label>{{"secret.secret" | translate}}</mat-label>
<textarea matInput [(ngModel)]="secret" <textarea matInput [(ngModel)]="secret"
[placeholder]="'secret.secret'|translate" [placeholder]="'secret.secret'|translate"
name="secret"></textarea> name="secret"></textarea>
</mat-form-field> </mat-form-field>
</mat-card-content>
<mat-card-actions>
<button mat-raised-button color="primary"
(click)="onUpdate()">{{"secret.update" | translate}}
</button>
</mat-card-actions>
</mat-card>
<mat-card style="margin-top: 2em" class="mat-elevation-z8">
<mat-card-header>
<mat-card-title>{{"secret.webhook_title" | translate}}</mat-card-title>
<mat-card-subtitle>{{"secret.webhook_subtitle" | translate}}</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<mat-form-field appearance="outline">
<mat-label>{{"secret.webhook_secret" | translate}}</mat-label>
<textarea matInput [(ngModel)]="webhookSecret"
[placeholder]="'secret.webhook_secret'|translate"
name="webhook_secret"></textarea>
</mat-form-field>
</mat-card-content>
<mat-card-actions> <mat-card-actions>
<button mat-raised-button [routerLink]="'../'">Back</button> <button mat-raised-button [routerLink]="'../'">Back</button>
<button mat-raised-button color="primary" <button mat-raised-button color="primary"
(click)="onUpdate()">{{"secret.update" | translate}}</button> (click)="onWebhookUpdate()">{{"secret.update" | translate}}</button>
</mat-card-actions> </mat-card-actions>
</mat-card> </mat-card>
</div> </div>

View File

@ -13,6 +13,7 @@ import {MessengerService} from "../messenger.service";
export class ProjectSecretComponent implements OnInit { export class ProjectSecretComponent implements OnInit {
secret: string; secret: string;
webhookSecret: string;
projectId: number; projectId: number;
constructor(private auth: AuthService, constructor(private auth: AuthService,
@ -26,6 +27,7 @@ export class ProjectSecretComponent implements OnInit {
this.route.params.subscribe(params => { this.route.params.subscribe(params => {
this.projectId = params["id"]; this.projectId = params["id"];
this.getSecret(); this.getSecret();
this.getWebhookSecret();
}); });
} }
@ -37,10 +39,28 @@ export class ProjectSecretComponent implements OnInit {
}) })
} }
getWebhookSecret() {
this.apiService.getWebhookSecret(this.projectId).subscribe(data => {
this.webhookSecret = data["content"]["webhook_secret"]
}, error => {
this.translate.get("messenger.unauthorized").subscribe(t => this.messenger.show(t))
})
}
onUpdate() { onUpdate() {
this.apiService.setSecret(this.projectId, this.secret).subscribe(data => { this.apiService.setSecret(this.projectId, this.secret).subscribe(data => {
this.translate.get("secret.ok").subscribe(t => this.messenger.show(t)) this.translate.get("secret.ok").subscribe(t => this.messenger.show(t))
}) })
}
onWebhookUpdate() {
this.apiService.setWebhookSecret(this.projectId, this.webhookSecret).subscribe(data => {
this.translate.get("secret.ok").subscribe(t => this.messenger.show(t))
})
}
refresh() {
this.getWebhookSecret();
this.getSecret();
} }
} }

View File

@ -8,7 +8,8 @@
"login": "Login", "login": "Login",
"worker_dashboard": "Workers", "worker_dashboard": "Workers",
"account": "Account", "account": "Account",
"manager_list": "Managers" "manager_list": "Managers",
"back": "back"
}, },
"logs": { "logs": {
"title": "Logs", "title": "Logs",
@ -119,6 +120,7 @@
"read": "Read", "read": "Read",
"edit": "Edit", "edit": "Edit",
"manage": "Manage permissions", "manage": "Manage permissions",
"secret": "Configure secrets",
"set": "Changes saved", "set": "Changes saved",
"workers": "Workers", "workers": "Workers",
"no_workers": "No workers have explicit access to this project", "no_workers": "No workers have explicit access to this project",
@ -152,6 +154,9 @@
"secret": { "secret": {
"title": "Project secret", "title": "Project secret",
"subtitle": "You can set project configuration here. It is only accessible by workers with ASSIGN access to this project", "subtitle": "You can set project configuration here. It is only accessible by workers with ASSIGN access to this project",
"webhook_title": "Webhook secret",
"webhook_subtitle": "You need to supply this to Github/Gogs for delivery to work",
"webhook_secret": "Webhook secret",
"secret": "Secret", "secret": "Secret",
"update": "Update", "update": "Update",
"ok": "Updated" "ok": "Updated"

View File

@ -8,7 +8,8 @@
"login": "Ouvrir un session", "login": "Ouvrir un session",
"worker_dashboard": "Workers", "worker_dashboard": "Workers",
"account": "Compte", "account": "Compte",
"manager_list": "Managers" "manager_list": "Managers",
"back": "Arrière"
}, },
"logs": { "logs": {
"title": "Journaux", "title": "Journaux",
@ -116,11 +117,11 @@
"read": "Lecture", "read": "Lecture",
"edit": "Modification", "edit": "Modification",
"manage": "Gestion des permissions", "manage": "Gestion des permissions",
"secret": "Gestion des secrets",
"set": "Changements enregistrés", "set": "Changements enregistrés",
"workers": "Workers", "workers": "Workers",
"no_workers": "Aucun Worker n'a explicitement accès à ce projet", "no_workers": "Aucun Worker n'a explicitement accès à ce projet",
"managers": "Managers", "managers": "Managers"
"secret": "Secret"
}, },
"messenger": { "messenger": {
"close": "Fermer", "close": "Fermer",
@ -148,8 +149,11 @@
"alias": "Alias pertinent" "alias": "Alias pertinent"
}, },
"secret": { "secret": {
"title": "Secret", "title": "Secret du projet",
"subtitle": "Vous pouvez définir la configuration du projet ici. Ce n'est accessible que par les Workers ayant accès au projet", "subtitle": "Vous pouvez définir la configuration du projet ici. Ce n'est accessible que par les Workers ayant accès au projet",
"webhook_title": "Secret (Webhook)",
"webhook_subtitle": "Vous devez le donner à Github/Gogs.",
"webhook_secret": "Secret (Webhook)",
"secret": "Secret", "secret": "Secret",
"update": "Mettre à jour", "update": "Mettre à jour",
"ok": "Mis à jour" "ok": "Mis à jour"