mirror of
https://github.com/SecurityBrewery/catalyst.git
synced 2026-06-15 21:38:39 +02:00
Remove user passwords (#539)
* Remove user passwords Co-authored-by: Jonas Plum <git@jonasplum.de>
This commit is contained in:
@@ -33,11 +33,10 @@ func (c *catalystResolver) UserCreateIfNotExists(ctx context.Context, user *maut
|
||||
_, err = c.database.UserCreateSetupAPIKey(ctx, password)
|
||||
} else {
|
||||
_, err = c.database.UserCreate(ctx, &model.UserForm{
|
||||
Apikey: user.APIKey,
|
||||
Blocked: user.Blocked,
|
||||
ID: user.ID,
|
||||
Password: &password,
|
||||
Roles: user.Roles,
|
||||
Apikey: user.APIKey,
|
||||
Blocked: user.Blocked,
|
||||
ID: user.ID,
|
||||
Roles: user.Roles,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -41,20 +41,20 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
_, _ = theCatalyst.DB.UserCreate(context.Background(), &model.UserForm{ID: "eve", Roles: []string{"admin"}, Password: pointer.String("eve")})
|
||||
_, _ = theCatalyst.DB.UserCreate(context.Background(), &model.UserForm{ID: "eve", Roles: []string{"admin"}})
|
||||
_ = theCatalyst.DB.UserDataCreate(context.Background(), "eve", &model.UserData{
|
||||
Name: pointer.String("Eve"),
|
||||
Email: pointer.String("eve@example.com"),
|
||||
Image: &avatarEve,
|
||||
})
|
||||
_, _ = theCatalyst.DB.UserCreate(context.Background(), &model.UserForm{ID: "kevin", Roles: []string{"admin"}, Password: pointer.String("kevin")})
|
||||
_, _ = theCatalyst.DB.UserCreate(context.Background(), &model.UserForm{ID: "kevin", Roles: []string{"admin"}})
|
||||
_ = theCatalyst.DB.UserDataCreate(context.Background(), "kevin", &model.UserData{
|
||||
Name: pointer.String("Kevin"),
|
||||
Email: pointer.String("kevin@example.com"),
|
||||
Image: &avatarKevin,
|
||||
})
|
||||
|
||||
_, _ = theCatalyst.DB.UserCreate(context.Background(), &model.UserForm{ID: "tom", Roles: []string{"admin"}, Password: pointer.String("tom")})
|
||||
_, _ = theCatalyst.DB.UserCreate(context.Background(), &model.UserForm{ID: "tom", Roles: []string{"admin"}})
|
||||
_ = theCatalyst.DB.UserDataCreate(context.Background(), "tom", &model.UserData{
|
||||
Name: pointer.String("tom"),
|
||||
Email: pointer.String("tom@example.com"),
|
||||
|
||||
@@ -61,8 +61,6 @@ func generateMigrations() ([]Migration, error) {
|
||||
|
||||
&updateDocument[model.Settings]{ID: "update-settings-global-1", Collection: "settings", Key: "global", Document: &model.Settings{ArtifactStates: []*model.Type{{Icon: "mdi-help-circle-outline", ID: "unknown", Name: "Unknown", Color: pointer.String(model.TypeColorInfo)}, {Icon: "mdi-skull", ID: "malicious", Name: "Malicious", Color: pointer.String(model.TypeColorError)}, {Icon: "mdi-check", ID: "clean", Name: "Clean", Color: pointer.String(model.TypeColorSuccess)}}, ArtifactKinds: []*model.Type{{Icon: "mdi-server", ID: "asset", Name: "Asset"}, {Icon: "mdi-bullseye", ID: "ioc", Name: "IOC"}}, Timeformat: "yyyy-MM-dd hh:mm:ss"}},
|
||||
|
||||
&updateSchema{ID: "update-user-simple-login", Name: "users", DataType: "user", Schema: `{"type":"object","properties":{"apikey":{"type":"boolean"},"blocked":{"type":"boolean"},"roles":{"items":{"type":"string"},"type":"array"},"salt":{"type":"string"},"sha256":{"type":"string"},"sha512":{"type":"string"}},"required":["blocked","apikey","roles"],"$id":"#/definitions/User"}`},
|
||||
|
||||
&mapRoles{ID: "simplify-roles"},
|
||||
}, nil
|
||||
}
|
||||
|
||||
+5
-28
@@ -3,7 +3,6 @@ package database
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
@@ -34,13 +33,11 @@ func generateKey() string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func toUser(user *model.UserForm, salt, sha256, sha512 *string) *model.User {
|
||||
func toUser(user *model.UserForm, sha256 *string) *model.User {
|
||||
u := &model.User{
|
||||
Blocked: user.Blocked,
|
||||
Roles: user.Roles,
|
||||
Salt: salt,
|
||||
Sha256: sha256,
|
||||
Sha512: sha512,
|
||||
Apikey: user.Apikey,
|
||||
}
|
||||
|
||||
@@ -89,16 +86,14 @@ func (db *Database) UserGetOrCreate(ctx context.Context, newUser *model.UserForm
|
||||
}
|
||||
|
||||
func (db *Database) UserCreate(ctx context.Context, newUser *model.UserForm) (*model.NewUserResponse, error) {
|
||||
var key, salt, sha256Hash, sha512Hash *string
|
||||
var key, sha256Hash *string
|
||||
if newUser.Apikey {
|
||||
key, sha256Hash = generateAPIKey()
|
||||
} else if newUser.Password != nil {
|
||||
salt, sha512Hash = hashUserPassword(newUser)
|
||||
}
|
||||
|
||||
var doc model.User
|
||||
newctx := driver.WithReturnNew(ctx, &doc)
|
||||
meta, err := db.userCollection.CreateDocument(ctx, newctx, strcase.ToKebab(newUser.ID), toUser(newUser, salt, sha256Hash, sha512Hash))
|
||||
meta, err := db.userCollection.CreateDocument(ctx, newctx, strcase.ToKebab(newUser.ID), toUser(newUser, sha256Hash))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -117,7 +112,7 @@ func (db *Database) UserCreateSetupAPIKey(ctx context.Context, key string) (*mod
|
||||
|
||||
var doc model.User
|
||||
newctx := driver.WithReturnNew(ctx, &doc)
|
||||
meta, err := db.userCollection.CreateDocument(ctx, newctx, strcase.ToKebab(newUser.ID), toUser(newUser, nil, sha256Hash, nil))
|
||||
meta, err := db.userCollection.CreateDocument(ctx, newctx, strcase.ToKebab(newUser.ID), toUser(newUser, sha256Hash))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -136,19 +131,11 @@ func (db *Database) UserUpdate(ctx context.Context, id string, user *model.UserF
|
||||
return nil, errors.New("cannot update an API key")
|
||||
}
|
||||
|
||||
var salt, sha512Hash *string
|
||||
if user.Password != nil {
|
||||
salt, sha512Hash = hashUserPassword(user)
|
||||
} else {
|
||||
salt = doc.Salt
|
||||
sha512Hash = doc.Sha512
|
||||
}
|
||||
|
||||
ctx = driver.WithReturnNew(ctx, &doc)
|
||||
|
||||
user.ID = id
|
||||
|
||||
meta, err := db.userCollection.ReplaceDocument(ctx, id, toUser(user, salt, nil, sha512Hash))
|
||||
meta, err := db.userCollection.ReplaceDocument(ctx, id, toUser(user, nil))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -244,13 +231,3 @@ func generateAPIKey() (key, sha256Hash *string) {
|
||||
|
||||
return &newKey, sha256Hash
|
||||
}
|
||||
|
||||
func hashUserPassword(newUser *model.UserForm) (salt, sha512Hash *string) {
|
||||
if newUser.Password != nil {
|
||||
saltKey := generateKey()
|
||||
salt = &saltKey
|
||||
sha512Hash = pointer.String(fmt.Sprintf("%x", sha512.Sum512([]byte(saltKey+*newUser.Password))))
|
||||
}
|
||||
|
||||
return salt, sha512Hash
|
||||
}
|
||||
|
||||
@@ -90,7 +90,6 @@ definitions:
|
||||
required: [ id, blocked, roles, apikey ]
|
||||
properties:
|
||||
id: { type: string }
|
||||
password: { type: string }
|
||||
blocked: { type: boolean }
|
||||
apikey: { type: boolean }
|
||||
roles: { type: array, items: { type: string } }
|
||||
@@ -102,9 +101,7 @@ definitions:
|
||||
blocked: { type: boolean }
|
||||
apikey: { type: boolean }
|
||||
roles: { type: array, items: { type: string } }
|
||||
salt: { type: string }
|
||||
sha256: { type: string } # for api keys
|
||||
sha512: { type: string } # for users
|
||||
|
||||
UserResponse:
|
||||
type: object
|
||||
|
||||
@@ -7102,14 +7102,8 @@
|
||||
},
|
||||
"type" : "array"
|
||||
},
|
||||
"salt" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"sha256" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"sha512" : {
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "apikey", "blocked", "roles" ],
|
||||
@@ -7168,9 +7162,6 @@
|
||||
"id" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"password" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"roles" : {
|
||||
"items" : {
|
||||
"type" : "string"
|
||||
|
||||
@@ -1285,12 +1285,8 @@ definitions:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
salt:
|
||||
type: string
|
||||
sha256:
|
||||
type: string
|
||||
sha512:
|
||||
type: string
|
||||
required:
|
||||
- blocked
|
||||
- apikey
|
||||
@@ -1338,8 +1334,6 @@ definitions:
|
||||
type: boolean
|
||||
id:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
roles:
|
||||
items:
|
||||
type: string
|
||||
|
||||
@@ -6523,14 +6523,8 @@
|
||||
},
|
||||
"type" : "array"
|
||||
},
|
||||
"salt" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"sha256" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"sha512" : {
|
||||
"type" : "string"
|
||||
}
|
||||
},
|
||||
"required" : [ "apikey", "blocked", "roles" ],
|
||||
@@ -6589,9 +6583,6 @@
|
||||
"id" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"password" : {
|
||||
"type" : "string"
|
||||
},
|
||||
"roles" : {
|
||||
"items" : {
|
||||
"type" : "string"
|
||||
|
||||
@@ -1166,12 +1166,8 @@ definitions:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
salt:
|
||||
type: string
|
||||
sha256:
|
||||
type: string
|
||||
sha512:
|
||||
type: string
|
||||
required:
|
||||
- blocked
|
||||
- apikey
|
||||
@@ -1219,8 +1215,6 @@ definitions:
|
||||
type: boolean
|
||||
id:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
roles:
|
||||
items:
|
||||
type: string
|
||||
|
||||
@@ -116,10 +116,10 @@ func init() {
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"default_groups":{"items":{"type":"string"},"type":"array"},"default_playbooks":{"items":{"type":"string"},"type":"array"},"default_template":{"type":"string"},"icon":{"type":"string"},"id":{"type":"string"},"name":{"type":"string"}},"required":["id","name","icon","default_template","default_playbooks"],"$id":"#/definitions/TicketTypeResponse"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"artifacts":{"items":{"$ref":"#/definitions/Artifact"},"type":"array"},"comments":{"items":{"$ref":"#/definitions/Comment"},"type":"array"},"created":{"format":"date-time","type":"string"},"details":{"type":"object"},"files":{"items":{"$ref":"#/definitions/File"},"type":"array"},"id":{"format":"int64","type":"integer"},"logs":{"items":{"$ref":"#/definitions/LogEntry"},"type":"array"},"modified":{"format":"date-time","type":"string"},"name":{"type":"string"},"owner":{"type":"string"},"playbooks":{"type":"object","additionalProperties":{"$ref":"#/definitions/PlaybookResponse"}},"read":{"items":{"type":"string"},"type":"array"},"references":{"items":{"$ref":"#/definitions/Reference"},"type":"array"},"schema":{"type":"string"},"status":{"type":"string"},"tickets":{"items":{"$ref":"#/definitions/TicketSimpleResponse"},"type":"array"},"type":{"type":"string"},"write":{"items":{"type":"string"},"type":"array"}},"required":["id","name","type","status","created","modified","schema"],"$id":"#/definitions/TicketWithTickets"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"color":{"title":"Color","type":"string","enum":["error","info","success","warning"]},"icon":{"title":"Icon (https://materialdesignicons.com)","type":"string"},"id":{"title":"ID","type":"string"},"name":{"title":"Name","type":"string"}},"required":["id","name","icon"],"$id":"#/definitions/Type"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"apikey":{"type":"boolean"},"blocked":{"type":"boolean"},"roles":{"items":{"type":"string"},"type":"array"},"salt":{"type":"string"},"sha256":{"type":"string"},"sha512":{"type":"string"}},"required":["blocked","apikey","roles"],"$id":"#/definitions/User"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"apikey":{"type":"boolean"},"blocked":{"type":"boolean"},"roles":{"items":{"type":"string"},"type":"array"},"sha256":{"type":"string"}},"required":["blocked","apikey","roles"],"$id":"#/definitions/User"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"email":{"type":"string"},"image":{"type":"string"},"name":{"type":"string"},"timeformat":{"title":"Time Format (https://moment.github.io/luxon/docs/manual/formatting.html#table-of-tokens)","type":"string"}},"$id":"#/definitions/UserData"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"email":{"type":"string"},"id":{"type":"string"},"image":{"type":"string"},"name":{"type":"string"},"timeformat":{"title":"Time Format (https://moment.github.io/luxon/docs/manual/formatting.html#table-of-tokens)","type":"string"}},"required":["id"],"$id":"#/definitions/UserDataResponse"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"apikey":{"type":"boolean"},"blocked":{"type":"boolean"},"id":{"type":"string"},"password":{"type":"string"},"roles":{"items":{"type":"string"},"type":"array"}},"required":["id","blocked","roles","apikey"],"$id":"#/definitions/UserForm"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"apikey":{"type":"boolean"},"blocked":{"type":"boolean"},"id":{"type":"string"},"roles":{"items":{"type":"string"},"type":"array"}},"required":["id","blocked","roles","apikey"],"$id":"#/definitions/UserForm"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"apikey":{"type":"boolean"},"blocked":{"type":"boolean"},"id":{"type":"string"},"roles":{"items":{"type":"string"},"type":"array"}},"required":["id","blocked","roles","apikey"],"$id":"#/definitions/UserResponse"}`),
|
||||
gojsonschema.NewStringLoader(`{"type":"object","properties":{"aggregation":{"type":"string"},"filter":{"type":"string"},"name":{"type":"string"},"type":{"type":"string","enum":["bar","line","pie"]},"width":{"maximum":12,"type":"integer"}},"required":["name","type","aggregation","width"],"$id":"#/definitions/Widget"}`),
|
||||
)
|
||||
@@ -588,9 +588,7 @@ type User struct {
|
||||
Apikey bool `json:"apikey"`
|
||||
Blocked bool `json:"blocked"`
|
||||
Roles []string `json:"roles"`
|
||||
Salt *string `json:"salt,omitempty"`
|
||||
Sha256 *string `json:"sha256,omitempty"`
|
||||
Sha512 *string `json:"sha512,omitempty"`
|
||||
}
|
||||
|
||||
type UserData struct {
|
||||
@@ -609,11 +607,10 @@ type UserDataResponse struct {
|
||||
}
|
||||
|
||||
type UserForm struct {
|
||||
Apikey bool `json:"apikey"`
|
||||
Blocked bool `json:"blocked"`
|
||||
ID string `json:"id"`
|
||||
Password *string `json:"password,omitempty"`
|
||||
Roles []string `json:"roles"`
|
||||
Apikey bool `json:"apikey"`
|
||||
Blocked bool `json:"blocked"`
|
||||
ID string `json:"id"`
|
||||
Roles []string `json:"roles"`
|
||||
}
|
||||
|
||||
type UserResponse struct {
|
||||
|
||||
@@ -2186,24 +2186,12 @@ export interface User {
|
||||
* @memberof User
|
||||
*/
|
||||
'roles': Array<string>;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof User
|
||||
*/
|
||||
'salt'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof User
|
||||
*/
|
||||
'sha256'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof User
|
||||
*/
|
||||
'sha512'?: string;
|
||||
}
|
||||
/**
|
||||
*
|
||||
@@ -2297,12 +2285,6 @@ export interface UserForm {
|
||||
* @memberof UserForm
|
||||
*/
|
||||
'id': string;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @memberof UserForm
|
||||
*/
|
||||
'password'?: string;
|
||||
/**
|
||||
*
|
||||
* @type {Array<string>}
|
||||
|
||||
@@ -130,6 +130,11 @@ export default Vue.extend({
|
||||
components: {
|
||||
VueCropper
|
||||
},
|
||||
watch: {
|
||||
"userdata": function () {
|
||||
this.editoruserdata = this.userdata;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
saveUserData: function() {
|
||||
this.$emit("save", this.editoruserdata);
|
||||
|
||||
@@ -17,11 +17,12 @@
|
||||
<span v-if="user.apikey">API Key</span>
|
||||
<span v-else>User</span>
|
||||
</h2>
|
||||
<i>Users can only be created via OIDC.</i>
|
||||
<v-form>
|
||||
<v-btn-toggle v-model="user.apikey" mandatory dense>
|
||||
<!--v-btn-toggle v-model="user.apikey" mandatory dense>
|
||||
<v-btn :value="false">User</v-btn>
|
||||
<v-btn :value="true">API Key</v-btn>
|
||||
</v-btn-toggle>
|
||||
</v-btn-toggle-->
|
||||
<v-text-field label="ID" v-model="user.id" class="mb-2" :rules="[
|
||||
v => !!v || 'ID is required',
|
||||
v => (v && v.length < 254) || 'ID must be between 1 and 254 characters',
|
||||
@@ -58,7 +59,7 @@
|
||||
<span v-if="user.apikey">(API Key)</span>
|
||||
</h2>
|
||||
|
||||
<v-text-field v-if="!user.apikey" label="New Password (leave empty to keep)" v-model="user.password" hide-details class="mb-4"></v-text-field>
|
||||
<!--v-text-field v-if="!user.apikey" label="New Password (leave empty to keep)" v-model="user.password" hide-details class="mb-4"></v-text-field-->
|
||||
<v-checkbox v-if="!user.apikey" label="Blocked" v-model="user.blocked" hide-details class="mb-4"></v-checkbox>
|
||||
|
||||
<v-select multiple chips v-if="!user.apikey" label="Roles" v-model="user.roles" :items="['analyst', 'engineer', 'admin']"></v-select>
|
||||
|
||||
Reference in New Issue
Block a user