Remove user passwords (#539)

* Remove user passwords

Co-authored-by: Jonas Plum <git@jonasplum.de>
This commit is contained in:
Jonas Plum
2022-10-22 15:12:37 +02:00
committed by GitHub
parent fb69a1a07b
commit 6756ce5426
13 changed files with 27 additions and 101 deletions

View File

@@ -33,11 +33,10 @@ func (c *catalystResolver) UserCreateIfNotExists(ctx context.Context, user *maut
_, err = c.database.UserCreateSetupAPIKey(ctx, password) _, err = c.database.UserCreateSetupAPIKey(ctx, password)
} else { } else {
_, err = c.database.UserCreate(ctx, &model.UserForm{ _, err = c.database.UserCreate(ctx, &model.UserForm{
Apikey: user.APIKey, Apikey: user.APIKey,
Blocked: user.Blocked, Blocked: user.Blocked,
ID: user.ID, ID: user.ID,
Password: &password, Roles: user.Roles,
Roles: user.Roles,
}) })
if err != nil { if err != nil {
return err return err

View File

@@ -41,20 +41,20 @@ func main() {
log.Fatal(err) 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{ _ = theCatalyst.DB.UserDataCreate(context.Background(), "eve", &model.UserData{
Name: pointer.String("Eve"), Name: pointer.String("Eve"),
Email: pointer.String("eve@example.com"), Email: pointer.String("eve@example.com"),
Image: &avatarEve, 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{ _ = theCatalyst.DB.UserDataCreate(context.Background(), "kevin", &model.UserData{
Name: pointer.String("Kevin"), Name: pointer.String("Kevin"),
Email: pointer.String("kevin@example.com"), Email: pointer.String("kevin@example.com"),
Image: &avatarKevin, 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{ _ = theCatalyst.DB.UserDataCreate(context.Background(), "tom", &model.UserData{
Name: pointer.String("tom"), Name: pointer.String("tom"),
Email: pointer.String("tom@example.com"), Email: pointer.String("tom@example.com"),

View File

@@ -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"}}, &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"}, &mapRoles{ID: "simplify-roles"},
}, nil }, nil
} }

View File

@@ -3,7 +3,6 @@ package database
import ( import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"crypto/sha512"
"errors" "errors"
"fmt" "fmt"
"log" "log"
@@ -34,13 +33,11 @@ func generateKey() string {
return string(b) 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{ u := &model.User{
Blocked: user.Blocked, Blocked: user.Blocked,
Roles: user.Roles, Roles: user.Roles,
Salt: salt,
Sha256: sha256, Sha256: sha256,
Sha512: sha512,
Apikey: user.Apikey, 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) { 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 { if newUser.Apikey {
key, sha256Hash = generateAPIKey() key, sha256Hash = generateAPIKey()
} else if newUser.Password != nil {
salt, sha512Hash = hashUserPassword(newUser)
} }
var doc model.User var doc model.User
newctx := driver.WithReturnNew(ctx, &doc) 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 { if err != nil {
return nil, err return nil, err
} }
@@ -117,7 +112,7 @@ func (db *Database) UserCreateSetupAPIKey(ctx context.Context, key string) (*mod
var doc model.User var doc model.User
newctx := driver.WithReturnNew(ctx, &doc) 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 { if err != nil {
return nil, err 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") 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) ctx = driver.WithReturnNew(ctx, &doc)
user.ID = id 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 { if err != nil {
return nil, err return nil, err
} }
@@ -244,13 +231,3 @@ func generateAPIKey() (key, sha256Hash *string) {
return &newKey, sha256Hash 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
}

View File

@@ -90,7 +90,6 @@ definitions:
required: [ id, blocked, roles, apikey ] required: [ id, blocked, roles, apikey ]
properties: properties:
id: { type: string } id: { type: string }
password: { type: string }
blocked: { type: boolean } blocked: { type: boolean }
apikey: { type: boolean } apikey: { type: boolean }
roles: { type: array, items: { type: string } } roles: { type: array, items: { type: string } }
@@ -102,9 +101,7 @@ definitions:
blocked: { type: boolean } blocked: { type: boolean }
apikey: { type: boolean } apikey: { type: boolean }
roles: { type: array, items: { type: string } } roles: { type: array, items: { type: string } }
salt: { type: string }
sha256: { type: string } # for api keys sha256: { type: string } # for api keys
sha512: { type: string } # for users
UserResponse: UserResponse:
type: object type: object

View File

@@ -7102,14 +7102,8 @@
}, },
"type" : "array" "type" : "array"
}, },
"salt" : {
"type" : "string"
},
"sha256" : { "sha256" : {
"type" : "string" "type" : "string"
},
"sha512" : {
"type" : "string"
} }
}, },
"required" : [ "apikey", "blocked", "roles" ], "required" : [ "apikey", "blocked", "roles" ],
@@ -7168,9 +7162,6 @@
"id" : { "id" : {
"type" : "string" "type" : "string"
}, },
"password" : {
"type" : "string"
},
"roles" : { "roles" : {
"items" : { "items" : {
"type" : "string" "type" : "string"

View File

@@ -1285,12 +1285,8 @@ definitions:
items: items:
type: string type: string
type: array type: array
salt:
type: string
sha256: sha256:
type: string type: string
sha512:
type: string
required: required:
- blocked - blocked
- apikey - apikey
@@ -1338,8 +1334,6 @@ definitions:
type: boolean type: boolean
id: id:
type: string type: string
password:
type: string
roles: roles:
items: items:
type: string type: string

View File

@@ -6523,14 +6523,8 @@
}, },
"type" : "array" "type" : "array"
}, },
"salt" : {
"type" : "string"
},
"sha256" : { "sha256" : {
"type" : "string" "type" : "string"
},
"sha512" : {
"type" : "string"
} }
}, },
"required" : [ "apikey", "blocked", "roles" ], "required" : [ "apikey", "blocked", "roles" ],
@@ -6589,9 +6583,6 @@
"id" : { "id" : {
"type" : "string" "type" : "string"
}, },
"password" : {
"type" : "string"
},
"roles" : { "roles" : {
"items" : { "items" : {
"type" : "string" "type" : "string"

View File

@@ -1166,12 +1166,8 @@ definitions:
items: items:
type: string type: string
type: array type: array
salt:
type: string
sha256: sha256:
type: string type: string
sha512:
type: string
required: required:
- blocked - blocked
- apikey - apikey
@@ -1219,8 +1215,6 @@ definitions:
type: boolean type: boolean
id: id:
type: string type: string
password:
type: string
roles: roles:
items: items:
type: string type: string

View File

@@ -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":{"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":{"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":{"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"},"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":{"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":{"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"}`), 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"` Apikey bool `json:"apikey"`
Blocked bool `json:"blocked"` Blocked bool `json:"blocked"`
Roles []string `json:"roles"` Roles []string `json:"roles"`
Salt *string `json:"salt,omitempty"`
Sha256 *string `json:"sha256,omitempty"` Sha256 *string `json:"sha256,omitempty"`
Sha512 *string `json:"sha512,omitempty"`
} }
type UserData struct { type UserData struct {
@@ -609,11 +607,10 @@ type UserDataResponse struct {
} }
type UserForm struct { type UserForm struct {
Apikey bool `json:"apikey"` Apikey bool `json:"apikey"`
Blocked bool `json:"blocked"` Blocked bool `json:"blocked"`
ID string `json:"id"` ID string `json:"id"`
Password *string `json:"password,omitempty"` Roles []string `json:"roles"`
Roles []string `json:"roles"`
} }
type UserResponse struct { type UserResponse struct {

View File

@@ -2186,24 +2186,12 @@ export interface User {
* @memberof User * @memberof User
*/ */
'roles': Array<string>; 'roles': Array<string>;
/**
*
* @type {string}
* @memberof User
*/
'salt'?: string;
/** /**
* *
* @type {string} * @type {string}
* @memberof User * @memberof User
*/ */
'sha256'?: string; 'sha256'?: string;
/**
*
* @type {string}
* @memberof User
*/
'sha512'?: string;
} }
/** /**
* *
@@ -2297,12 +2285,6 @@ export interface UserForm {
* @memberof UserForm * @memberof UserForm
*/ */
'id': string; 'id': string;
/**
*
* @type {string}
* @memberof UserForm
*/
'password'?: string;
/** /**
* *
* @type {Array<string>} * @type {Array<string>}

View File

@@ -130,6 +130,11 @@ export default Vue.extend({
components: { components: {
VueCropper VueCropper
}, },
watch: {
"userdata": function () {
this.editoruserdata = this.userdata;
},
},
methods: { methods: {
saveUserData: function() { saveUserData: function() {
this.$emit("save", this.editoruserdata); this.$emit("save", this.editoruserdata);

View File

@@ -17,11 +17,12 @@
<span v-if="user.apikey">API Key</span> <span v-if="user.apikey">API Key</span>
<span v-else>User</span> <span v-else>User</span>
</h2> </h2>
<i>Users can only be created via OIDC.</i>
<v-form> <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="false">User</v-btn>
<v-btn :value="true">API Key</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-text-field label="ID" v-model="user.id" class="mb-2" :rules="[
v => !!v || 'ID is required', v => !!v || 'ID is required',
v => (v && v.length < 254) || 'ID must be between 1 and 254 characters', 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> <span v-if="user.apikey">(API Key)</span>
</h2> </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-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> <v-select multiple chips v-if="!user.apikey" label="Roles" v-model="user.roles" :items="['analyst', 'engineer', 'admin']"></v-select>