rework user and admin profiles

users and admins can now also update their email and description
This commit is contained in:
Nicola Murino 2021-09-29 18:46:15 +02:00
parent af8fa7ff81
commit ba1febba73
No known key found for this signature in database
GPG key ID: 2F1FB59433D5A8CB
25 changed files with 1038 additions and 798 deletions

View file

@ -45,7 +45,7 @@ jobs:
go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/version.date=$DATE_TIME" -o sftpgo.exe go build -trimpath -ldflags "-s -w -X github.com/drakkan/sftpgo/v2/version.commit=$GIT_COMMIT -X github.com/drakkan/sftpgo/v2/version.date=$DATE_TIME" -o sftpgo.exe
- name: Run test cases using SQLite provider - name: Run test cases using SQLite provider
run: go test -v -p 1 -timeout 10m ./... -coverprofile=coverage.txt -covermode=atomic run: go test -v -p 1 -timeout 15m ./... -coverprofile=coverage.txt -covermode=atomic
- name: Upload coverage to Codecov - name: Upload coverage to Codecov
if: ${{ matrix.upload-coverage }} if: ${{ matrix.upload-coverage }}
@ -69,7 +69,7 @@ jobs:
SFTPGO_DATA_PROVIDER__NAME: 'sftpgo_bolt.db' SFTPGO_DATA_PROVIDER__NAME: 'sftpgo_bolt.db'
- name: Run test cases using memory provider - name: Run test cases using memory provider
run: go test -v -p 1 -timeout 10m ./... -covermode=atomic run: go test -v -p 1 -timeout 15m ./... -covermode=atomic
env: env:
SFTPGO_DATA_PROVIDER__DRIVER: memory SFTPGO_DATA_PROVIDER__DRIVER: memory
SFTPGO_DATA_PROVIDER__NAME: '' SFTPGO_DATA_PROVIDER__NAME: ''
@ -108,7 +108,7 @@ jobs:
path: output path: output
test-goarch-386: test-goarch-386:
name: Run test cases on 32 bit arch name: Run test cases on 32-bit arch
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
@ -125,7 +125,7 @@ jobs:
GOARCH: 386 GOARCH: 386
- name: Run test cases - name: Run test cases
run: go test -v -p 1 -timeout 10m ./... -covermode=atomic run: go test -v -p 1 -timeout 15m ./... -covermode=atomic
env: env:
SFTPGO_DATA_PROVIDER__DRIVER: memory SFTPGO_DATA_PROVIDER__DRIVER: memory
SFTPGO_DATA_PROVIDER__NAME: '' SFTPGO_DATA_PROVIDER__NAME: ''
@ -177,7 +177,7 @@ jobs:
- name: Run tests using PostgreSQL provider - name: Run tests using PostgreSQL provider
run: | run: |
go test -v -p 1 -timeout 10m ./... -covermode=atomic go test -v -p 1 -timeout 15m ./... -covermode=atomic
env: env:
SFTPGO_DATA_PROVIDER__DRIVER: postgresql SFTPGO_DATA_PROVIDER__DRIVER: postgresql
SFTPGO_DATA_PROVIDER__NAME: sftpgo SFTPGO_DATA_PROVIDER__NAME: sftpgo
@ -188,7 +188,7 @@ jobs:
- name: Run tests using MySQL provider - name: Run tests using MySQL provider
run: | run: |
go test -v -p 1 -timeout 10m ./... -covermode=atomic go test -v -p 1 -timeout 15m ./... -covermode=atomic
env: env:
SFTPGO_DATA_PROVIDER__DRIVER: mysql SFTPGO_DATA_PROVIDER__DRIVER: mysql
SFTPGO_DATA_PROVIDER__NAME: sftpgo SFTPGO_DATA_PROVIDER__NAME: sftpgo
@ -201,7 +201,7 @@ jobs:
run: | run: |
docker run --rm --name crdb --health-cmd "curl -I http://127.0.0.1:8080" --health-interval 10s --health-timeout 5s --health-retries 6 -p 26257:26257 -d cockroachdb/cockroach:latest start-single-node --insecure --listen-addr 0.0.0.0:26257 docker run --rm --name crdb --health-cmd "curl -I http://127.0.0.1:8080" --health-interval 10s --health-timeout 5s --health-retries 6 -p 26257:26257 -d cockroachdb/cockroach:latest start-single-node --insecure --listen-addr 0.0.0.0:26257
docker exec crdb cockroach sql --insecure -e 'create database "sftpgo"' docker exec crdb cockroach sql --insecure -e 'create database "sftpgo"'
go test -v -p 1 -timeout 10m ./... -covermode=atomic go test -v -p 1 -timeout 15m ./... -covermode=atomic
docker stop crdb docker stop crdb
env: env:
SFTPGO_DATA_PROVIDER__DRIVER: cockroachdb SFTPGO_DATA_PROVIDER__DRIVER: cockroachdb

View file

@ -736,6 +736,11 @@ func (u *User) CanChangeAPIKeyAuth() bool {
return !util.IsStringInSlice(sdk.WebClientAPIKeyAuthChangeDisabled, u.Filters.WebClient) return !util.IsStringInSlice(sdk.WebClientAPIKeyAuthChangeDisabled, u.Filters.WebClient)
} }
// CanChangeInfo returns true if this user is allowed to change its info such as email and description
func (u *User) CanChangeInfo() bool {
return !util.IsStringInSlice(sdk.WebClientInfoChangeDisabled, u.Filters.WebClient)
}
// CanManagePublicKeys returns true if this user is allowed to manage public keys // CanManagePublicKeys returns true if this user is allowed to manage public keys
// from the web client. Used in web client UI // from the web client. Used in web client UI
func (u *User) CanManagePublicKeys() bool { func (u *User) CanManagePublicKeys() bool {

View file

@ -252,9 +252,9 @@ The configuration file contains the following sections:
- `url`, string, optional. If not empty, the header will be added only if the request URL starts with the one specified here - `url`, string, optional. If not empty, the header will be added only if the request URL starts with the one specified here
- **kms**, configuration for the Key Management Service, more details can be found [here](./kms.md) - **kms**, configuration for the Key Management Service, more details can be found [here](./kms.md)
- `secrets` - `secrets`
- `url`, string. Defines the URI to the KMS service. Default empty. - `url`, string. Defines the URI to the KMS service. Default: empty.
- `master_key`, string. Defines the master encryption key as string. If not empty, it takes precedence over `master_key_path`. Default empty. - `master_key`, string. Defines the master encryption key as string. If not empty, it takes precedence over `master_key_path`. Default: empty.
- `master_key_path, string. Defines the absolute path to a file containing the master encryption key. Default empty. - `master_key_path, string. Defines the absolute path to a file containing the master encryption key. Default: empty.
- **mfa**, multi-factor authentication settings - **mfa**, multi-factor authentication settings
- `totp`, list of struct that define settings for time-based one time passwords (RFC 6238). Each struct has the following fields: - `totp`, list of struct that define settings for time-based one time passwords (RFC 6238). Each struct has the following fields:
- `name`, string. Unique configuration name. This name should not be changed if there are users or admins using the configuration. The name is not exposed to the authentication apps. Default: `Default`. - `name`, string. Unique configuration name. This name should not be changed if there are users or admins using the configuration. The name is not exposed to the authentication apps. Default: `Default`.

16
go.mod
View file

@ -7,8 +7,8 @@ require (
github.com/Azure/azure-storage-blob-go v0.14.0 github.com/Azure/azure-storage-blob-go v0.14.0
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962
github.com/alexedwards/argon2id v0.0.0-20210511081203-7d35d68092b8 github.com/alexedwards/argon2id v0.0.0-20210511081203-7d35d68092b8
github.com/aws/aws-sdk-go v1.40.49 github.com/aws/aws-sdk-go v1.40.51
github.com/cockroachdb/cockroach-go/v2 v2.1.1 github.com/cockroachdb/cockroach-go/v2 v2.2.0
github.com/eikenb/pipeat v0.0.0-20210603033007-44fc3ffce52b github.com/eikenb/pipeat v0.0.0-20210603033007-44fc3ffce52b
github.com/fatih/color v1.13.0 // indirect github.com/fatih/color v1.13.0 // indirect
github.com/fclairamb/ftpserverlib v0.16.0 github.com/fclairamb/ftpserverlib v0.16.0
@ -43,7 +43,7 @@ require (
github.com/pkg/sftp v1.13.4 github.com/pkg/sftp v1.13.4
github.com/pquerna/otp v1.3.0 github.com/pquerna/otp v1.3.0
github.com/prometheus/client_golang v1.11.0 github.com/prometheus/client_golang v1.11.0
github.com/prometheus/common v0.31.0 // indirect github.com/prometheus/common v0.31.1 // indirect
github.com/rs/cors v1.8.0 github.com/rs/cors v1.8.0
github.com/rs/xid v1.3.0 github.com/rs/xid v1.3.0
github.com/rs/zerolog v1.25.0 github.com/rs/zerolog v1.25.0
@ -62,17 +62,17 @@ require (
gocloud.dev v0.24.0 gocloud.dev v0.24.0
golang.org/x/crypto v0.0.0-20210915214749-c084706c2272 golang.org/x/crypto v0.0.0-20210915214749-c084706c2272
golang.org/x/net v0.0.0-20210924151903-3ad01bbaa167 golang.org/x/net v0.0.0-20210924151903-3ad01bbaa167
golang.org/x/sys v0.0.0-20210927052749-1cf2251ac284 golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
google.golang.org/api v0.57.0 google.golang.org/api v0.58.0
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0 // indirect google.golang.org/genproto v0.0.0-20210928142010-c7af6a1a74c9 // indirect
google.golang.org/grpc v1.41.0 google.golang.org/grpc v1.41.0
google.golang.org/protobuf v1.27.1 google.golang.org/protobuf v1.27.1
gopkg.in/natefinch/lumberjack.v2 v2.0.0 gopkg.in/natefinch/lumberjack.v2 v2.0.0
) )
require ( require (
cloud.google.com/go v0.95.0 // indirect cloud.google.com/go v0.96.0 // indirect
github.com/Azure/azure-pipeline-go v0.2.3 // indirect github.com/Azure/azure-pipeline-go v0.2.3 // indirect
github.com/StackExchange/wmi v1.2.1 // indirect github.com/StackExchange/wmi v1.2.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
@ -85,7 +85,7 @@ require (
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.0 // indirect
github.com/fsnotify/fsnotify v1.5.1 // indirect github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/go-ole/go-ole v1.2.5 // indirect github.com/go-ole/go-ole v1.2.5 // indirect
github.com/goccy/go-json v0.7.8 // indirect github.com/goccy/go-json v0.7.9 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.2 // indirect github.com/golang/protobuf v1.5.2 // indirect
github.com/google/go-cmp v0.5.6 // indirect github.com/google/go-cmp v0.5.6 // indirect

34
go.sum
View file

@ -32,8 +32,8 @@ cloud.google.com/go v0.92.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+Y
cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
cloud.google.com/go v0.94.0/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.94.0/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
cloud.google.com/go v0.95.0 h1:JVWssQIj9cLwHmLjqWLptFa83o7HgqUictM6eyvGWJE= cloud.google.com/go v0.96.0 h1:r9XIwQ9FrJspMjHulRm1kl1uanw5gSolzSK+dukeH0E=
cloud.google.com/go v0.95.0/go.mod h1:MzZUAH870Y7E+c14j23Ir66FC1+PK8WLG7OG4SjP+0k= cloud.google.com/go v0.96.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@ -136,8 +136,8 @@ github.com/aws/aws-sdk-go v1.15.27/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZo
github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= github.com/aws/aws-sdk-go v1.38.68/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go v1.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go v1.40.34/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
github.com/aws/aws-sdk-go v1.40.49 h1:kIbJYc4FZA2r4yxNU5giIR4HHLRkG9roFReWAsk0ZVQ= github.com/aws/aws-sdk-go v1.40.51 h1:FfxDcjWqhMGwy+raf5Zf6AH8qsHIl9YG2dvJIBx1Aw4=
github.com/aws/aws-sdk-go v1.40.49/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= github.com/aws/aws-sdk-go v1.40.51/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q=
github.com/aws/aws-sdk-go-v2 v1.7.0/go.mod h1:tb9wi5s61kTDA5qCkcDbt3KRVV74GGslQkl/DRdX/P4= github.com/aws/aws-sdk-go-v2 v1.7.0/go.mod h1:tb9wi5s61kTDA5qCkcDbt3KRVV74GGslQkl/DRdX/P4=
github.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= github.com/aws/aws-sdk-go-v2 v1.9.0/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
github.com/aws/aws-sdk-go-v2/config v1.7.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY= github.com/aws/aws-sdk-go-v2/config v1.7.0/go.mod h1:w9+nMZ7soXCe5nT46Ri354SNhXDQ6v+V5wqDjnZE+GY=
@ -184,8 +184,8 @@ github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnht
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/cockroach-go/v2 v2.1.1 h1:3XzfSMuUT0wBe1a3o5C0eOTcArhmmFAg2Jzh/7hhKqo= github.com/cockroachdb/cockroach-go/v2 v2.2.0 h1:/5znzg5n373N/3ESjHF5SMLxiW4RKB05Ql//KWfeTFs=
github.com/cockroachdb/cockroach-go/v2 v2.1.1/go.mod h1:7NtUnP6eK+l6k483WSYNrq3Kb23bWV10IRV1TyeSpwM= github.com/cockroachdb/cockroach-go/v2 v2.2.0/go.mod h1:u3MiKYGupPPjkn3ozknpMUpxPaNLTFWAya419/zv6eI=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
@ -288,9 +288,12 @@ github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22
github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/goccy/go-json v0.7.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.7.6/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.7.8 h1:CvMH7LotYymYuLGEohBM1lTZWX4g6jzWUUl2aLFuBoE=
github.com/goccy/go-json v0.7.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/goccy/go-json v0.7.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/goccy/go-json v0.7.9 h1:mSp3uo1tr6MXQTYopSNhHTUnJhd2zQ4Yk+HdJZP+ZRY=
github.com/goccy/go-json v0.7.9/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
@ -681,8 +684,8 @@ github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6T
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
github.com/prometheus/common v0.31.0 h1:FTJdLTjtrh4dXlCjpzdZJXMnejSTL5F/nVQm5sNwD34= github.com/prometheus/common v0.31.1 h1:d18hG4PkHnNAKNMOmFuXFaiY8Us0nird/2m60uS1AMs=
github.com/prometheus/common v0.31.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= github.com/prometheus/common v0.31.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
@ -954,8 +957,9 @@ golang.org/x/sys v0.0.0-20210816074244-15123e1e1f71/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210831042530-f4d43177bf5e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927052749-1cf2251ac284 h1:lBPNCmq8u4zFP3huKCmUQ2Fx8kcY4X+O12UgGnyKsrg= golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927052749-1cf2251ac284/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1079,8 +1083,9 @@ google.golang.org/api v0.52.0/go.mod h1:Him/adpjt0sxtkWViy0b6xyKW/SD71CwdJ7HqJo7
google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
google.golang.org/api v0.57.0 h1:4t9zuDlHLcIx0ZEhmXEeFVCRsiOgpgn2QOH9N0MNjPI=
google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
google.golang.org/api v0.58.0 h1:MDkAbYIB1JpSgCTOCYYoIec/coMlKK4oVbpnBLLcyT0=
google.golang.org/api v0.58.0/go.mod h1:cAbP2FsxoGVNwtgNAmmn3y5G1TWAiVYRmg4yku3lv+E=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -1152,9 +1157,10 @@ google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEc
google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210921142501-181ce0d877f6/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0 h1:5Tbluzus3QxoAJx4IefGt1W0HQZW4nuMrVk684jI74Q=
google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20210928142010-c7af6a1a74c9 h1:XTH066D35LyHehRwlYhoK3qA+Hcgvg5xREG4kFQEW1Y=
google.golang.org/genproto v0.0.0-20210928142010-c7af6a1a74c9/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.8.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=

View file

@ -155,7 +155,7 @@ func deleteAdmin(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "Admin deleted", http.StatusOK) sendAPIResponse(w, r, err, "Admin deleted", http.StatusOK)
} }
func getAdminAPIKeyAuthStatus(w http.ResponseWriter, r *http.Request) { func getAdminProfile(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
claims, err := getTokenClaims(r) claims, err := getTokenClaims(r)
if err != nil || claims.Username == "" { if err != nil || claims.Username == "" {
@ -167,13 +167,17 @@ func getAdminAPIKeyAuthStatus(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "", getRespStatus(err)) sendAPIResponse(w, r, err, "", getRespStatus(err))
return return
} }
resp := apiKeyAuth{ resp := adminProfile{
AllowAPIKeyAuth: admin.Filters.AllowAPIKeyAuth, baseProfile: baseProfile{
Email: admin.Email,
Description: admin.Description,
AllowAPIKeyAuth: admin.Filters.AllowAPIKeyAuth,
},
} }
render.JSON(w, r, resp) render.JSON(w, r, resp)
} }
func changeAdminAPIKeyAuthStatus(w http.ResponseWriter, r *http.Request) { func updateAdminProfile(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
claims, err := getTokenClaims(r) claims, err := getTokenClaims(r)
if err != nil || claims.Username == "" { if err != nil || claims.Username == "" {
@ -185,18 +189,20 @@ func changeAdminAPIKeyAuthStatus(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "", getRespStatus(err)) sendAPIResponse(w, r, err, "", getRespStatus(err))
return return
} }
var req apiKeyAuth var req adminProfile
err = render.DecodeJSON(r.Body, &req) err = render.DecodeJSON(r.Body, &req)
if err != nil { if err != nil {
sendAPIResponse(w, r, err, "", http.StatusBadRequest) sendAPIResponse(w, r, err, "", http.StatusBadRequest)
return return
} }
admin.Email = req.Email
admin.Description = req.Description
admin.Filters.AllowAPIKeyAuth = req.AllowAPIKeyAuth admin.Filters.AllowAPIKeyAuth = req.AllowAPIKeyAuth
if err := dataprovider.UpdateAdmin(&admin); err != nil { if err := dataprovider.UpdateAdmin(&admin); err != nil {
sendAPIResponse(w, r, err, "", getRespStatus(err)) sendAPIResponse(w, r, err, "", getRespStatus(err))
return return
} }
sendAPIResponse(w, r, err, "API key authentication status updated", http.StatusOK) sendAPIResponse(w, r, err, "Profile updated", http.StatusOK)
} }
func changeAdminPassword(w http.ResponseWriter, r *http.Request) { func changeAdminPassword(w http.ResponseWriter, r *http.Request) {

View file

@ -349,7 +349,7 @@ func setUserPublicKeys(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "Public keys updated", http.StatusOK) sendAPIResponse(w, r, err, "Public keys updated", http.StatusOK)
} }
func getUserAPIKeyAuthStatus(w http.ResponseWriter, r *http.Request) { func getUserProfile(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
claims, err := getTokenClaims(r) claims, err := getTokenClaims(r)
if err != nil || claims.Username == "" { if err != nil || claims.Username == "" {
@ -361,20 +361,25 @@ func getUserAPIKeyAuthStatus(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "", getRespStatus(err)) sendAPIResponse(w, r, err, "", getRespStatus(err))
return return
} }
resp := apiKeyAuth{ resp := userProfile{
AllowAPIKeyAuth: user.Filters.AllowAPIKeyAuth, baseProfile: baseProfile{
Email: user.Email,
Description: user.Description,
AllowAPIKeyAuth: user.Filters.AllowAPIKeyAuth,
},
PublicKeys: user.PublicKeys,
} }
render.JSON(w, r, resp) render.JSON(w, r, resp)
} }
func changeUserAPIKeyAuthStatus(w http.ResponseWriter, r *http.Request) { func updateUserProfile(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
claims, err := getTokenClaims(r) claims, err := getTokenClaims(r)
if err != nil || claims.Username == "" { if err != nil || claims.Username == "" {
sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest) sendAPIResponse(w, r, err, "Invalid token claims", http.StatusBadRequest)
return return
} }
var req apiKeyAuth var req userProfile
err = render.DecodeJSON(r.Body, &req) err = render.DecodeJSON(r.Body, &req)
if err != nil { if err != nil {
sendAPIResponse(w, r, err, "", http.StatusBadRequest) sendAPIResponse(w, r, err, "", http.StatusBadRequest)
@ -385,12 +390,25 @@ func changeUserAPIKeyAuthStatus(w http.ResponseWriter, r *http.Request) {
sendAPIResponse(w, r, err, "", getRespStatus(err)) sendAPIResponse(w, r, err, "", getRespStatus(err))
return return
} }
user.Filters.AllowAPIKeyAuth = req.AllowAPIKeyAuth if !user.CanManagePublicKeys() && !user.CanChangeAPIKeyAuth() && !user.CanChangeInfo() {
sendAPIResponse(w, r, nil, "You are not allowed to change anything", http.StatusForbidden)
return
}
if user.CanManagePublicKeys() {
user.PublicKeys = req.PublicKeys
}
if user.CanChangeAPIKeyAuth() {
user.Filters.AllowAPIKeyAuth = req.AllowAPIKeyAuth
}
if user.CanChangeInfo() {
user.Email = req.Email
user.Description = req.Description
}
if err := dataprovider.UpdateUser(&user); err != nil { if err := dataprovider.UpdateUser(&user); err != nil {
sendAPIResponse(w, r, err, "", getRespStatus(err)) sendAPIResponse(w, r, err, "", getRespStatus(err))
return return
} }
sendAPIResponse(w, r, err, "API key authentication status updated", http.StatusOK) sendAPIResponse(w, r, err, "Profile updated", http.StatusOK)
} }
func changeUserPassword(w http.ResponseWriter, r *http.Request) { func changeUserPassword(w http.ResponseWriter, r *http.Request) {

View file

@ -28,8 +28,19 @@ type pwdChange struct {
NewPassword string `json:"new_password"` NewPassword string `json:"new_password"`
} }
type apiKeyAuth struct { type baseProfile struct {
AllowAPIKeyAuth bool `json:"allow_api_key_auth"` Email string `json:"email,omitempty"`
Description string `json:"description,omitempty"`
AllowAPIKeyAuth bool `json:"allow_api_key_auth"`
}
type adminProfile struct {
baseProfile
}
type userProfile struct {
baseProfile
PublicKeys []string `json:"public_keys,omitempty"`
} }
func sendAPIResponse(w http.ResponseWriter, r *http.Request, err error, message string, code int) { func sendAPIResponse(w http.ResponseWriter, r *http.Request, err error, message string, code int) {

View file

@ -31,104 +31,101 @@ import (
) )
const ( const (
logSender = "httpd" logSender = "httpd"
tokenPath = "/api/v2/token" tokenPath = "/api/v2/token"
logoutPath = "/api/v2/logout" logoutPath = "/api/v2/logout"
userTokenPath = "/api/v2/user/token" userTokenPath = "/api/v2/user/token"
userLogoutPath = "/api/v2/user/logout" userLogoutPath = "/api/v2/user/logout"
activeConnectionsPath = "/api/v2/connections" activeConnectionsPath = "/api/v2/connections"
quotasBasePath = "/api/v2/quotas" quotasBasePath = "/api/v2/quotas"
quotaScanPath = "/api/v2/quota-scans" quotaScanPath = "/api/v2/quota-scans"
quotaScanVFolderPath = "/api/v2/folder-quota-scans" quotaScanVFolderPath = "/api/v2/folder-quota-scans"
userPath = "/api/v2/users" userPath = "/api/v2/users"
versionPath = "/api/v2/version" versionPath = "/api/v2/version"
folderPath = "/api/v2/folders" folderPath = "/api/v2/folders"
serverStatusPath = "/api/v2/status" serverStatusPath = "/api/v2/status"
dumpDataPath = "/api/v2/dumpdata" dumpDataPath = "/api/v2/dumpdata"
loadDataPath = "/api/v2/loaddata" loadDataPath = "/api/v2/loaddata"
updateUsedQuotaPath = "/api/v2/quota-update" updateUsedQuotaPath = "/api/v2/quota-update"
updateFolderUsedQuotaPath = "/api/v2/folder-quota-update" updateFolderUsedQuotaPath = "/api/v2/folder-quota-update"
defenderHosts = "/api/v2/defender/hosts" defenderHosts = "/api/v2/defender/hosts"
defenderBanTime = "/api/v2/defender/bantime" defenderBanTime = "/api/v2/defender/bantime"
defenderUnban = "/api/v2/defender/unban" defenderUnban = "/api/v2/defender/unban"
defenderScore = "/api/v2/defender/score" defenderScore = "/api/v2/defender/score"
adminPath = "/api/v2/admins" adminPath = "/api/v2/admins"
adminPwdPath = "/api/v2/admin/changepwd" adminPwdPath = "/api/v2/admin/changepwd"
adminPwdCompatPath = "/api/v2/changepwd/admin" adminPwdCompatPath = "/api/v2/changepwd/admin"
adminManageAPIKeyPath = "/api/v2/admin/apikeyauth" adminProfilePath = "/api/v2/admin/profile"
userPwdPath = "/api/v2/user/changepwd" userPwdPath = "/api/v2/user/changepwd"
userPublicKeysPath = "/api/v2/user/publickeys" userPublicKeysPath = "/api/v2/user/publickeys"
userFolderPath = "/api/v2/user/folder" userFolderPath = "/api/v2/user/folder"
userDirsPath = "/api/v2/user/dirs" userDirsPath = "/api/v2/user/dirs"
userFilePath = "/api/v2/user/file" userFilePath = "/api/v2/user/file"
userFilesPath = "/api/v2/user/files" userFilesPath = "/api/v2/user/files"
userStreamZipPath = "/api/v2/user/streamzip" userStreamZipPath = "/api/v2/user/streamzip"
apiKeysPath = "/api/v2/apikeys" apiKeysPath = "/api/v2/apikeys"
adminTOTPConfigsPath = "/api/v2/admin/totp/configs" adminTOTPConfigsPath = "/api/v2/admin/totp/configs"
adminTOTPGeneratePath = "/api/v2/admin/totp/generate" adminTOTPGeneratePath = "/api/v2/admin/totp/generate"
adminTOTPValidatePath = "/api/v2/admin/totp/validate" adminTOTPValidatePath = "/api/v2/admin/totp/validate"
adminTOTPSavePath = "/api/v2/admin/totp/save" adminTOTPSavePath = "/api/v2/admin/totp/save"
admin2FARecoveryCodesPath = "/api/v2/admin/2fa/recoverycodes" admin2FARecoveryCodesPath = "/api/v2/admin/2fa/recoverycodes"
userTOTPConfigsPath = "/api/v2/user/totp/configs" userTOTPConfigsPath = "/api/v2/user/totp/configs"
userTOTPGeneratePath = "/api/v2/user/totp/generate" userTOTPGeneratePath = "/api/v2/user/totp/generate"
userTOTPValidatePath = "/api/v2/user/totp/validate" userTOTPValidatePath = "/api/v2/user/totp/validate"
userTOTPSavePath = "/api/v2/user/totp/save" userTOTPSavePath = "/api/v2/user/totp/save"
user2FARecoveryCodesPath = "/api/v2/user/2fa/recoverycodes" user2FARecoveryCodesPath = "/api/v2/user/2fa/recoverycodes"
userManageAPIKeyPath = "/api/v2/user/apikeyauth" userProfilePath = "/api/v2/user/profile"
retentionBasePath = "/api/v2/retention/users" retentionBasePath = "/api/v2/retention/users"
retentionChecksPath = "/api/v2/retention/users/checks" retentionChecksPath = "/api/v2/retention/users/checks"
healthzPath = "/healthz" healthzPath = "/healthz"
webRootPathDefault = "/" webRootPathDefault = "/"
webBasePathDefault = "/web" webBasePathDefault = "/web"
webBasePathAdminDefault = "/web/admin" webBasePathAdminDefault = "/web/admin"
webBasePathClientDefault = "/web/client" webBasePathClientDefault = "/web/client"
webAdminSetupPathDefault = "/web/admin/setup" webAdminSetupPathDefault = "/web/admin/setup"
webLoginPathDefault = "/web/admin/login" webLoginPathDefault = "/web/admin/login"
webAdminTwoFactorPathDefault = "/web/admin/twofactor" webAdminTwoFactorPathDefault = "/web/admin/twofactor"
webAdminTwoFactorRecoveryPathDefault = "/web/admin/twofactor-recovery" webAdminTwoFactorRecoveryPathDefault = "/web/admin/twofactor-recovery"
webLogoutPathDefault = "/web/admin/logout" webLogoutPathDefault = "/web/admin/logout"
webUsersPathDefault = "/web/admin/users" webUsersPathDefault = "/web/admin/users"
webUserPathDefault = "/web/admin/user" webUserPathDefault = "/web/admin/user"
webConnectionsPathDefault = "/web/admin/connections" webConnectionsPathDefault = "/web/admin/connections"
webFoldersPathDefault = "/web/admin/folders" webFoldersPathDefault = "/web/admin/folders"
webFolderPathDefault = "/web/admin/folder" webFolderPathDefault = "/web/admin/folder"
webStatusPathDefault = "/web/admin/status" webStatusPathDefault = "/web/admin/status"
webAdminsPathDefault = "/web/admin/managers" webAdminsPathDefault = "/web/admin/managers"
webAdminPathDefault = "/web/admin/manager" webAdminPathDefault = "/web/admin/manager"
webMaintenancePathDefault = "/web/admin/maintenance" webMaintenancePathDefault = "/web/admin/maintenance"
webBackupPathDefault = "/web/admin/backup" webBackupPathDefault = "/web/admin/backup"
webRestorePathDefault = "/web/admin/restore" webRestorePathDefault = "/web/admin/restore"
webScanVFolderPathDefault = "/web/admin/quotas/scanfolder" webScanVFolderPathDefault = "/web/admin/quotas/scanfolder"
webQuotaScanPathDefault = "/web/admin/quotas/scanuser" webQuotaScanPathDefault = "/web/admin/quotas/scanuser"
webChangeAdminPwdPathDefault = "/web/admin/changepwd" webChangeAdminPwdPathDefault = "/web/admin/changepwd"
webAdminCredentialsPathDefault = "/web/admin/credentials" webAdminProfilePathDefault = "/web/admin/profile"
webAdminMFAPathDefault = "/web/admin/mfa" webAdminMFAPathDefault = "/web/admin/mfa"
webAdminTOTPGeneratePathDefault = "/web/admin/totp/generate" webAdminTOTPGeneratePathDefault = "/web/admin/totp/generate"
webAdminTOTPValidatePathDefault = "/web/admin/totp/validate" webAdminTOTPValidatePathDefault = "/web/admin/totp/validate"
webAdminTOTPSavePathDefault = "/web/admin/totp/save" webAdminTOTPSavePathDefault = "/web/admin/totp/save"
webAdminRecoveryCodesPathDefault = "/web/admin/recoverycodes" webAdminRecoveryCodesPathDefault = "/web/admin/recoverycodes"
webChangeAdminAPIKeyAccessPathDefault = "/web/admin/apikeyaccess" webTemplateUserDefault = "/web/admin/template/user"
webTemplateUserDefault = "/web/admin/template/user" webTemplateFolderDefault = "/web/admin/template/folder"
webTemplateFolderDefault = "/web/admin/template/folder" webDefenderPathDefault = "/web/admin/defender"
webDefenderPathDefault = "/web/admin/defender" webDefenderHostsPathDefault = "/web/admin/defender/hosts"
webDefenderHostsPathDefault = "/web/admin/defender/hosts" webClientLoginPathDefault = "/web/client/login"
webClientLoginPathDefault = "/web/client/login" webClientTwoFactorPathDefault = "/web/client/twofactor"
webClientTwoFactorPathDefault = "/web/client/twofactor" webClientTwoFactorRecoveryPathDefault = "/web/client/twofactor-recovery"
webClientTwoFactorRecoveryPathDefault = "/web/client/twofactor-recovery" webClientFilesPathDefault = "/web/client/files"
webClientFilesPathDefault = "/web/client/files" webClientDirsPathDefault = "/web/client/dirs"
webClientDirsPathDefault = "/web/client/dirs" webClientDownloadZipPathDefault = "/web/client/downloadzip"
webClientDownloadZipPathDefault = "/web/client/downloadzip" webClientProfilePathDefault = "/web/client/profile"
webClientCredentialsPathDefault = "/web/client/credentials" webClientMFAPathDefault = "/web/client/mfa"
webClientMFAPathDefault = "/web/client/mfa" webClientTOTPGeneratePathDefault = "/web/client/totp/generate"
webClientTOTPGeneratePathDefault = "/web/client/totp/generate" webClientTOTPValidatePathDefault = "/web/client/totp/validate"
webClientTOTPValidatePathDefault = "/web/client/totp/validate" webClientTOTPSavePathDefault = "/web/client/totp/save"
webClientTOTPSavePathDefault = "/web/client/totp/save" webClientRecoveryCodesPathDefault = "/web/client/recoverycodes"
webClientRecoveryCodesPathDefault = "/web/client/recoverycodes" webChangeClientPwdPathDefault = "/web/client/changepwd"
webChangeClientPwdPathDefault = "/web/client/changepwd" webClientLogoutPathDefault = "/web/client/logout"
webChangeClientKeysPathDefault = "/web/client/managekeys" webStaticFilesPathDefault = "/static"
webChangeClientAPIKeyAccessPathDefault = "/web/client/apikeyaccess"
webClientLogoutPathDefault = "/web/client/logout"
webStaticFilesPathDefault = "/static"
// MaxRestoreSize defines the max size for the loaddata input file // MaxRestoreSize defines the max size for the loaddata input file
MaxRestoreSize = 10485760 // 10 MB MaxRestoreSize = 10485760 // 10 MB
maxRequestSize = 1048576 // 1MB maxRequestSize = 1048576 // 1MB
@ -139,63 +136,60 @@ const (
) )
var ( var (
backupsPath string backupsPath string
certMgr *common.CertManager certMgr *common.CertManager
cleanupTicker *time.Ticker cleanupTicker *time.Ticker
cleanupDone chan bool cleanupDone chan bool
invalidatedJWTTokens sync.Map invalidatedJWTTokens sync.Map
csrfTokenAuth *jwtauth.JWTAuth csrfTokenAuth *jwtauth.JWTAuth
webRootPath string webRootPath string
webBasePath string webBasePath string
webBaseAdminPath string webBaseAdminPath string
webBaseClientPath string webBaseClientPath string
webAdminSetupPath string webAdminSetupPath string
webLoginPath string webLoginPath string
webAdminTwoFactorPath string webAdminTwoFactorPath string
webAdminTwoFactorRecoveryPath string webAdminTwoFactorRecoveryPath string
webLogoutPath string webLogoutPath string
webUsersPath string webUsersPath string
webUserPath string webUserPath string
webConnectionsPath string webConnectionsPath string
webFoldersPath string webFoldersPath string
webFolderPath string webFolderPath string
webStatusPath string webStatusPath string
webAdminsPath string webAdminsPath string
webAdminPath string webAdminPath string
webMaintenancePath string webMaintenancePath string
webBackupPath string webBackupPath string
webRestorePath string webRestorePath string
webScanVFolderPath string webScanVFolderPath string
webQuotaScanPath string webQuotaScanPath string
webAdminCredentialsPath string webAdminProfilePath string
webAdminMFAPath string webAdminMFAPath string
webAdminTOTPGeneratePath string webAdminTOTPGeneratePath string
webAdminTOTPValidatePath string webAdminTOTPValidatePath string
webAdminTOTPSavePath string webAdminTOTPSavePath string
webAdminRecoveryCodesPath string webAdminRecoveryCodesPath string
webChangeAdminAPIKeyAccessPath string webChangeAdminPwdPath string
webChangeAdminPwdPath string webTemplateUser string
webTemplateUser string webTemplateFolder string
webTemplateFolder string webDefenderPath string
webDefenderPath string webDefenderHostsPath string
webDefenderHostsPath string webClientLoginPath string
webClientLoginPath string webClientTwoFactorPath string
webClientTwoFactorPath string webClientTwoFactorRecoveryPath string
webClientTwoFactorRecoveryPath string webClientFilesPath string
webClientFilesPath string webClientDirsPath string
webClientDirsPath string webClientDownloadZipPath string
webClientDownloadZipPath string webClientProfilePath string
webClientCredentialsPath string webChangeClientPwdPath string
webChangeClientPwdPath string webClientMFAPath string
webChangeClientKeysPath string webClientTOTPGeneratePath string
webClientMFAPath string webClientTOTPValidatePath string
webClientTOTPGeneratePath string webClientTOTPSavePath string
webClientTOTPValidatePath string webClientRecoveryCodesPath string
webClientTOTPSavePath string webClientLogoutPath string
webClientRecoveryCodesPath string webStaticFilesPath string
webChangeClientAPIKeyAccessPath string
webClientLogoutPath string
webStaticFilesPath string
// max upload size for http clients, 1GB by default // max upload size for http clients, 1GB by default
maxUploadFileSize = int64(1048576000) maxUploadFileSize = int64(1048576000)
) )
@ -530,10 +524,8 @@ func updateWebClientURLs(baseURL string) {
webClientFilesPath = path.Join(baseURL, webClientFilesPathDefault) webClientFilesPath = path.Join(baseURL, webClientFilesPathDefault)
webClientDirsPath = path.Join(baseURL, webClientDirsPathDefault) webClientDirsPath = path.Join(baseURL, webClientDirsPathDefault)
webClientDownloadZipPath = path.Join(baseURL, webClientDownloadZipPathDefault) webClientDownloadZipPath = path.Join(baseURL, webClientDownloadZipPathDefault)
webClientCredentialsPath = path.Join(baseURL, webClientCredentialsPathDefault) webClientProfilePath = path.Join(baseURL, webClientProfilePathDefault)
webChangeClientPwdPath = path.Join(baseURL, webChangeClientPwdPathDefault) webChangeClientPwdPath = path.Join(baseURL, webChangeClientPwdPathDefault)
webChangeClientKeysPath = path.Join(baseURL, webChangeClientKeysPathDefault)
webChangeClientAPIKeyAccessPath = path.Join(baseURL, webChangeClientAPIKeyAccessPathDefault)
webClientLogoutPath = path.Join(baseURL, webClientLogoutPathDefault) webClientLogoutPath = path.Join(baseURL, webClientLogoutPathDefault)
webClientMFAPath = path.Join(baseURL, webClientMFAPathDefault) webClientMFAPath = path.Join(baseURL, webClientMFAPathDefault)
webClientTOTPGeneratePath = path.Join(baseURL, webClientTOTPGeneratePathDefault) webClientTOTPGeneratePath = path.Join(baseURL, webClientTOTPGeneratePathDefault)
@ -568,13 +560,12 @@ func updateWebAdminURLs(baseURL string) {
webScanVFolderPath = path.Join(baseURL, webScanVFolderPathDefault) webScanVFolderPath = path.Join(baseURL, webScanVFolderPathDefault)
webQuotaScanPath = path.Join(baseURL, webQuotaScanPathDefault) webQuotaScanPath = path.Join(baseURL, webQuotaScanPathDefault)
webChangeAdminPwdPath = path.Join(baseURL, webChangeAdminPwdPathDefault) webChangeAdminPwdPath = path.Join(baseURL, webChangeAdminPwdPathDefault)
webAdminCredentialsPath = path.Join(baseURL, webAdminCredentialsPathDefault) webAdminProfilePath = path.Join(baseURL, webAdminProfilePathDefault)
webAdminMFAPath = path.Join(baseURL, webAdminMFAPathDefault) webAdminMFAPath = path.Join(baseURL, webAdminMFAPathDefault)
webAdminTOTPGeneratePath = path.Join(baseURL, webAdminTOTPGeneratePathDefault) webAdminTOTPGeneratePath = path.Join(baseURL, webAdminTOTPGeneratePathDefault)
webAdminTOTPValidatePath = path.Join(baseURL, webAdminTOTPValidatePathDefault) webAdminTOTPValidatePath = path.Join(baseURL, webAdminTOTPValidatePathDefault)
webAdminTOTPSavePath = path.Join(baseURL, webAdminTOTPSavePathDefault) webAdminTOTPSavePath = path.Join(baseURL, webAdminTOTPSavePathDefault)
webAdminRecoveryCodesPath = path.Join(baseURL, webAdminRecoveryCodesPathDefault) webAdminRecoveryCodesPath = path.Join(baseURL, webAdminRecoveryCodesPathDefault)
webChangeAdminAPIKeyAccessPath = path.Join(baseURL, webChangeAdminAPIKeyAccessPathDefault)
webTemplateUser = path.Join(baseURL, webTemplateUserDefault) webTemplateUser = path.Join(baseURL, webTemplateUserDefault)
webTemplateFolder = path.Join(baseURL, webTemplateFolderDefault) webTemplateFolder = path.Join(baseURL, webTemplateFolderDefault)
webDefenderHostsPath = path.Join(baseURL, webDefenderHostsPathDefault) webDefenderHostsPath = path.Join(baseURL, webDefenderHostsPathDefault)

View file

@ -92,13 +92,13 @@ const (
adminTOTPValidatePath = "/api/v2/admin/totp/validate" adminTOTPValidatePath = "/api/v2/admin/totp/validate"
adminTOTPSavePath = "/api/v2/admin/totp/save" adminTOTPSavePath = "/api/v2/admin/totp/save"
admin2FARecoveryCodesPath = "/api/v2/admin/2fa/recoverycodes" admin2FARecoveryCodesPath = "/api/v2/admin/2fa/recoverycodes"
adminManageAPIKeyPath = "/api/v2/admin/apikeyauth" adminProfilePath = "/api/v2/admin/profile"
userTOTPConfigsPath = "/api/v2/user/totp/configs" userTOTPConfigsPath = "/api/v2/user/totp/configs"
userTOTPGeneratePath = "/api/v2/user/totp/generate" userTOTPGeneratePath = "/api/v2/user/totp/generate"
userTOTPValidatePath = "/api/v2/user/totp/validate" userTOTPValidatePath = "/api/v2/user/totp/validate"
userTOTPSavePath = "/api/v2/user/totp/save" userTOTPSavePath = "/api/v2/user/totp/save"
user2FARecoveryCodesPath = "/api/v2/user/2fa/recoverycodes" user2FARecoveryCodesPath = "/api/v2/user/2fa/recoverycodes"
userManageAPIKeyPath = "/api/v2/user/apikeyauth" userProfilePath = "/api/v2/user/profile"
retentionBasePath = "/api/v2/retention/users" retentionBasePath = "/api/v2/retention/users"
healthzPath = "/healthz" healthzPath = "/healthz"
webBasePath = "/web" webBasePath = "/web"
@ -117,11 +117,10 @@ const (
webMaintenancePath = "/web/admin/maintenance" webMaintenancePath = "/web/admin/maintenance"
webRestorePath = "/web/admin/restore" webRestorePath = "/web/admin/restore"
webChangeAdminPwdPath = "/web/admin/changepwd" webChangeAdminPwdPath = "/web/admin/changepwd"
webAdminCredentialsPath = "/web/admin/credentials" webAdminProfilePath = "/web/admin/profile"
webTemplateUser = "/web/admin/template/user" webTemplateUser = "/web/admin/template/user"
webTemplateFolder = "/web/admin/template/folder" webTemplateFolder = "/web/admin/template/folder"
webDefenderPath = "/web/admin/defender" webDefenderPath = "/web/admin/defender"
webChangeAdminAPIKeyAccessPath = "/web/admin/apikeyaccess"
webAdminTwoFactorPath = "/web/admin/twofactor" webAdminTwoFactorPath = "/web/admin/twofactor"
webAdminTwoFactorRecoveryPath = "/web/admin/twofactor-recovery" webAdminTwoFactorRecoveryPath = "/web/admin/twofactor-recovery"
webAdminMFAPath = "/web/admin/mfa" webAdminMFAPath = "/web/admin/mfa"
@ -131,10 +130,8 @@ const (
webClientFilesPath = "/web/client/files" webClientFilesPath = "/web/client/files"
webClientDirsPath = "/web/client/dirs" webClientDirsPath = "/web/client/dirs"
webClientDownloadZipPath = "/web/client/downloadzip" webClientDownloadZipPath = "/web/client/downloadzip"
webClientCredentialsPath = "/web/client/credentials"
webChangeClientPwdPath = "/web/client/changepwd" webChangeClientPwdPath = "/web/client/changepwd"
webChangeClientKeysPath = "/web/client/managekeys" webClientProfilePath = "/web/client/profile"
webChangeClientAPIKeyAccessPath = "/web/client/apikeyaccess"
webClientTwoFactorPath = "/web/client/twofactor" webClientTwoFactorPath = "/web/client/twofactor"
webClientTwoFactorRecoveryPath = "/web/client/twofactor-recovery" webClientTwoFactorRecoveryPath = "/web/client/twofactor-recovery"
webClientLogoutPath = "/web/client/logout" webClientLogoutPath = "/web/client/logout"
@ -3408,7 +3405,7 @@ func TestSkipNaturalKeysValidation(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
form := make(url.Values) form := make(url.Values)
form.Set(csrfFormToken, csrfToken) form.Set(csrfFormToken, csrfToken)
req, err := http.NewRequest(http.MethodPost, webChangeClientAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode()))) req, err := http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
assert.NoError(t, err) assert.NoError(t, err)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, token) setJWTCookieForReq(req, token)
@ -3438,7 +3435,7 @@ func TestSkipNaturalKeysValidation(t *testing.T) {
apiKeyAuthReq["allow_api_key_auth"] = true apiKeyAuthReq["allow_api_key_auth"] = true
asJSON, err := json.Marshal(apiKeyAuthReq) asJSON, err := json.Marshal(apiKeyAuthReq)
assert.NoError(t, err) assert.NoError(t, err)
req, err = http.NewRequest(http.MethodPut, userManageAPIKeyPath, bytes.NewBuffer(asJSON)) req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
assert.NoError(t, err) assert.NoError(t, err)
setBearerForReq(req, userAPIToken) setBearerForReq(req, userAPIToken)
rr = executeRequest(req) rr = executeRequest(req)
@ -3456,7 +3453,7 @@ func TestSkipNaturalKeysValidation(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
form = make(url.Values) form = make(url.Values)
form.Set(csrfFormToken, csrfToken) form.Set(csrfFormToken, csrfToken)
req, _ = http.NewRequest(http.MethodPost, webChangeAdminAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode()))) req, _ = http.NewRequest(http.MethodPost, webAdminProfilePath, bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, token) setJWTCookieForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
@ -3467,7 +3464,7 @@ func TestSkipNaturalKeysValidation(t *testing.T) {
apiKeyAuthReq["allow_api_key_auth"] = true apiKeyAuthReq["allow_api_key_auth"] = true
asJSON, err = json.Marshal(apiKeyAuthReq) asJSON, err = json.Marshal(apiKeyAuthReq)
assert.NoError(t, err) assert.NoError(t, err)
req, err = http.NewRequest(http.MethodPut, adminManageAPIKeyPath, bytes.NewBuffer(asJSON)) req, err = http.NewRequest(http.MethodPut, adminProfilePath, bytes.NewBuffer(asJSON))
assert.NoError(t, err) assert.NoError(t, err)
setBearerForReq(req, adminAPIToken) setBearerForReq(req, adminAPIToken)
rr = executeRequest(req) rr = executeRequest(req)
@ -5128,6 +5125,40 @@ func TestChangeAdminPwdInvalidJsonMock(t *testing.T) {
checkResponseCode(t, http.StatusBadRequest, rr) checkResponseCode(t, http.StatusBadRequest, rr)
} }
func TestMFAPermission(t *testing.T) {
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
assert.NoError(t, err)
webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
assert.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, webClientMFAPath, nil)
assert.NoError(t, err)
req.RequestURI = webClientMFAPath
setJWTCookieForReq(req, webToken)
rr := executeRequest(req)
checkResponseCode(t, http.StatusOK, rr)
user.Filters.WebClient = []string{sdk.WebClientMFADisabled}
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
webToken, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
assert.NoError(t, err)
req, err = http.NewRequest(http.MethodGet, webClientMFAPath, nil)
assert.NoError(t, err)
req.RequestURI = webClientMFAPath
setJWTCookieForReq(req, webToken)
rr = executeRequest(req)
checkResponseCode(t, http.StatusForbidden, rr)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestWebUserTwoFactorLogin(t *testing.T) { func TestWebUserTwoFactorLogin(t *testing.T) {
u := getTestUser() u := getTestUser()
user, _, err := httpdtest.AddUser(u, http.StatusCreated) user, _, err := httpdtest.AddUser(u, http.StatusCreated)
@ -5958,104 +5989,160 @@ func TestWebUserTOTP(t *testing.T) {
checkResponseCode(t, http.StatusNotFound, rr) checkResponseCode(t, http.StatusNotFound, rr)
} }
func TestWebAPIChangeUserAPIKeyAuth(t *testing.T) { func TestWebAPIChangeUserProfileMock(t *testing.T) {
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated) user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
assert.NoError(t, err) assert.NoError(t, err)
assert.False(t, user.Filters.AllowAPIKeyAuth) assert.False(t, user.Filters.AllowAPIKeyAuth)
token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword) token, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
assert.NoError(t, err) assert.NoError(t, err)
// invalid json // invalid json
req, err := http.NewRequest(http.MethodPut, userManageAPIKeyPath, bytes.NewBuffer([]byte("{"))) req, err := http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer([]byte("{")))
assert.NoError(t, err) assert.NoError(t, err)
setBearerForReq(req, token) setBearerForReq(req, token)
rr := executeRequest(req) rr := executeRequest(req)
checkResponseCode(t, http.StatusBadRequest, rr) checkResponseCode(t, http.StatusBadRequest, rr)
apiKeyAuthReq := make(map[string]bool) email := "userapi@example.com"
apiKeyAuthReq["allow_api_key_auth"] = true description := "user API description"
asJSON, err := json.Marshal(apiKeyAuthReq) profileReq := make(map[string]interface{})
profileReq["allow_api_key_auth"] = true
profileReq["email"] = email
profileReq["description"] = description
profileReq["public_keys"] = []string{testPubKey, testPubKey1}
asJSON, err := json.Marshal(profileReq)
assert.NoError(t, err) assert.NoError(t, err)
req, err = http.NewRequest(http.MethodPut, userManageAPIKeyPath, bytes.NewBuffer(asJSON)) req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
assert.NoError(t, err) assert.NoError(t, err)
setBearerForReq(req, token) setBearerForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr) checkResponseCode(t, http.StatusOK, rr)
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK) profileReq = make(map[string]interface{})
assert.NoError(t, err) req, err = http.NewRequest(http.MethodGet, userProfilePath, nil)
assert.True(t, user.Filters.AllowAPIKeyAuth)
apiKeyAuthReq = make(map[string]bool)
req, err = http.NewRequest(http.MethodGet, userManageAPIKeyPath, nil)
assert.NoError(t, err) assert.NoError(t, err)
setBearerForReq(req, token) setBearerForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr) checkResponseCode(t, http.StatusOK, rr)
err = json.Unmarshal(rr.Body.Bytes(), &apiKeyAuthReq) err = json.Unmarshal(rr.Body.Bytes(), &profileReq)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, apiKeyAuthReq["allow_api_key_auth"]) assert.Equal(t, email, profileReq["email"].(string))
assert.Equal(t, description, profileReq["description"].(string))
assert.True(t, profileReq["allow_api_key_auth"].(bool))
assert.Len(t, profileReq["public_keys"].([]interface{}), 2)
// set an invalid email
profileReq = make(map[string]interface{})
profileReq["email"] = "notavalidemail"
asJSON, err = json.Marshal(profileReq)
assert.NoError(t, err)
req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
assert.NoError(t, err)
setBearerForReq(req, token)
rr = executeRequest(req)
checkResponseCode(t, http.StatusBadRequest, rr)
assert.Contains(t, rr.Body.String(), "Validation error: email")
// set an invalid public key
profileReq = make(map[string]interface{})
profileReq["public_keys"] = []string{"not a public key"}
asJSON, err = json.Marshal(profileReq)
assert.NoError(t, err)
req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
assert.NoError(t, err)
setBearerForReq(req, token)
rr = executeRequest(req)
checkResponseCode(t, http.StatusBadRequest, rr)
assert.Contains(t, rr.Body.String(), "Validation error: could not parse key")
apiKeyAuthReq["allow_api_key_auth"] = false user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientPubKeyChangeDisabled}
asJSON, err = json.Marshal(apiKeyAuthReq) user.Email = email
user.Description = description
user.Filters.AllowAPIKeyAuth = true
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err) assert.NoError(t, err)
req, err = http.NewRequest(http.MethodPut, userManageAPIKeyPath, bytes.NewBuffer(asJSON)) token, err = getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
assert.NoError(t, err)
profileReq = make(map[string]interface{})
profileReq["allow_api_key_auth"] = false
profileReq["email"] = email
profileReq["description"] = description + "_mod"
profileReq["public_keys"] = []string{testPubKey}
asJSON, err = json.Marshal(profileReq)
assert.NoError(t, err)
req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
assert.NoError(t, err) assert.NoError(t, err)
setBearerForReq(req, token) setBearerForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr) checkResponseCode(t, http.StatusOK, rr)
assert.Contains(t, rr.Body.String(), "Profile updated")
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK) // check that api key auth and public keys were not changed
assert.NoError(t, err) profileReq = make(map[string]interface{})
assert.False(t, user.Filters.AllowAPIKeyAuth) req, err = http.NewRequest(http.MethodGet, userProfilePath, nil)
apiKeyAuthReq = make(map[string]bool)
req, err = http.NewRequest(http.MethodGet, userManageAPIKeyPath, nil)
assert.NoError(t, err) assert.NoError(t, err)
setBearerForReq(req, token) setBearerForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr) checkResponseCode(t, http.StatusOK, rr)
err = json.Unmarshal(rr.Body.Bytes(), &apiKeyAuthReq) err = json.Unmarshal(rr.Body.Bytes(), &profileReq)
assert.NoError(t, err) assert.NoError(t, err)
assert.False(t, apiKeyAuthReq["allow_api_key_auth"]) assert.Equal(t, email, profileReq["email"].(string))
assert.Equal(t, description+"_mod", profileReq["description"].(string))
assert.True(t, profileReq["allow_api_key_auth"].(bool))
assert.Len(t, profileReq["public_keys"].([]interface{}), 2)
// remove the permission user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientInfoChangeDisabled}
user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled} user.Description = description + "_mod"
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "") _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err) assert.NoError(t, err)
assert.Len(t, user.Filters.WebClient, 1) token, err = getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
assert.Contains(t, user.Filters.WebClient, sdk.WebClientAPIKeyAuthChangeDisabled)
newToken, err := getJWTAPIUserTokenFromTestServer(defaultUsername, defaultPassword)
assert.NoError(t, err) assert.NoError(t, err)
apiKeyAuthReq["allow_api_key_auth"] = true profileReq = make(map[string]interface{})
asJSON, err = json.Marshal(apiKeyAuthReq) profileReq["allow_api_key_auth"] = false
profileReq["email"] = "newemail@apiuser.com"
profileReq["description"] = description
profileReq["public_keys"] = []string{testPubKey}
asJSON, err = json.Marshal(profileReq)
assert.NoError(t, err) assert.NoError(t, err)
req, err = http.NewRequest(http.MethodPut, userManageAPIKeyPath, bytes.NewBuffer(asJSON)) req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
assert.NoError(t, err) assert.NoError(t, err)
setBearerForReq(req, newToken) setBearerForReq(req, token)
rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr)
profileReq = make(map[string]interface{})
req, err = http.NewRequest(http.MethodGet, userProfilePath, nil)
assert.NoError(t, err)
setBearerForReq(req, token)
rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr)
err = json.Unmarshal(rr.Body.Bytes(), &profileReq)
assert.NoError(t, err)
assert.Equal(t, email, profileReq["email"].(string))
assert.Equal(t, description+"_mod", profileReq["description"].(string))
assert.True(t, profileReq["allow_api_key_auth"].(bool))
assert.Len(t, profileReq["public_keys"].([]interface{}), 1)
// finally disable all profile permissions
user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientInfoChangeDisabled,
sdk.WebClientPubKeyChangeDisabled}
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
assert.NoError(t, err)
setBearerForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusForbidden, rr) checkResponseCode(t, http.StatusForbidden, rr)
// get will still work assert.Contains(t, rr.Body.String(), "You are not allowed to change anything")
req, err = http.NewRequest(http.MethodGet, userManageAPIKeyPath, nil)
assert.NoError(t, err)
setBearerForReq(req, newToken)
rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr)
_, err = httpdtest.RemoveUser(user, http.StatusOK) _, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir()) err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err) assert.NoError(t, err)
apiKeyAuthReq = make(map[string]bool) req, err = http.NewRequest(http.MethodGet, userProfilePath, nil)
req, err = http.NewRequest(http.MethodGet, userManageAPIKeyPath, nil)
assert.NoError(t, err) assert.NoError(t, err)
setBearerForReq(req, token) setBearerForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusNotFound, rr) checkResponseCode(t, http.StatusNotFound, rr)
req, err = http.NewRequest(http.MethodPut, userManageAPIKeyPath, bytes.NewBuffer(asJSON)) req, err = http.NewRequest(http.MethodPut, userProfilePath, bytes.NewBuffer(asJSON))
assert.NoError(t, err) assert.NoError(t, err)
setBearerForReq(req, token) setBearerForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
@ -6136,7 +6223,7 @@ func TestLoginInvalidPasswordMock(t *testing.T) {
assert.Equal(t, http.StatusUnauthorized, rr.Code) assert.Equal(t, http.StatusUnauthorized, rr.Code)
} }
func TestChangeAdminAPIKeyAuth(t *testing.T) { func TestWebAPIChangeAdminProfileMock(t *testing.T) {
admin := getTestAdmin() admin := getTestAdmin()
admin.Username = altAdminUsername admin.Username = altAdminUsername
admin.Password = altAdminPassword admin.Password = altAdminPassword
@ -6147,65 +6234,59 @@ func TestChangeAdminAPIKeyAuth(t *testing.T) {
token, err := getJWTAPITokenFromTestServer(altAdminUsername, altAdminPassword) token, err := getJWTAPITokenFromTestServer(altAdminUsername, altAdminPassword)
assert.NoError(t, err) assert.NoError(t, err)
// invalid json // invalid json
req, err := http.NewRequest(http.MethodPut, adminManageAPIKeyPath, bytes.NewBuffer([]byte("{"))) req, err := http.NewRequest(http.MethodPut, adminProfilePath, bytes.NewBuffer([]byte("{")))
assert.NoError(t, err) assert.NoError(t, err)
setBearerForReq(req, token) setBearerForReq(req, token)
rr := executeRequest(req) rr := executeRequest(req)
checkResponseCode(t, http.StatusBadRequest, rr) checkResponseCode(t, http.StatusBadRequest, rr)
apiKeyAuthReq := make(map[string]bool) email := "adminapi@example.com"
apiKeyAuthReq["allow_api_key_auth"] = true description := "admin API description"
asJSON, err := json.Marshal(apiKeyAuthReq) profileReq := make(map[string]interface{})
profileReq["allow_api_key_auth"] = true
profileReq["email"] = email
profileReq["description"] = description
asJSON, err := json.Marshal(profileReq)
assert.NoError(t, err) assert.NoError(t, err)
req, err = http.NewRequest(http.MethodPut, adminManageAPIKeyPath, bytes.NewBuffer(asJSON)) req, err = http.NewRequest(http.MethodPut, adminProfilePath, bytes.NewBuffer(asJSON))
assert.NoError(t, err) assert.NoError(t, err)
setBearerForReq(req, token) setBearerForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr) checkResponseCode(t, http.StatusOK, rr)
assert.Contains(t, rr.Body.String(), "Profile updated")
admin, _, err = httpdtest.GetAdminByUsername(altAdminUsername, http.StatusOK) profileReq = make(map[string]interface{})
assert.NoError(t, err) req, err = http.NewRequest(http.MethodGet, adminProfilePath, nil)
assert.True(t, admin.Filters.AllowAPIKeyAuth)
apiKeyAuthReq = make(map[string]bool)
req, err = http.NewRequest(http.MethodGet, adminManageAPIKeyPath, nil)
assert.NoError(t, err) assert.NoError(t, err)
setBearerForReq(req, token) setBearerForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr) checkResponseCode(t, http.StatusOK, rr)
err = json.Unmarshal(rr.Body.Bytes(), &apiKeyAuthReq) err = json.Unmarshal(rr.Body.Bytes(), &profileReq)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, apiKeyAuthReq["allow_api_key_auth"]) assert.Equal(t, email, profileReq["email"].(string))
assert.Equal(t, description, profileReq["description"].(string))
apiKeyAuthReq["allow_api_key_auth"] = false assert.True(t, profileReq["allow_api_key_auth"].(bool))
asJSON, err = json.Marshal(apiKeyAuthReq) // set an invalid email
profileReq["email"] = "admin_invalid_email"
asJSON, err = json.Marshal(profileReq)
assert.NoError(t, err) assert.NoError(t, err)
req, err = http.NewRequest(http.MethodPut, adminManageAPIKeyPath, bytes.NewBuffer(asJSON)) req, err = http.NewRequest(http.MethodPut, adminProfilePath, bytes.NewBuffer(asJSON))
assert.NoError(t, err) assert.NoError(t, err)
setBearerForReq(req, token) setBearerForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr) checkResponseCode(t, http.StatusBadRequest, rr)
assert.Contains(t, rr.Body.String(), "Validation error: email")
apiKeyAuthReq = make(map[string]bool)
req, err = http.NewRequest(http.MethodGet, adminManageAPIKeyPath, nil)
assert.NoError(t, err)
setBearerForReq(req, token)
rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr)
err = json.Unmarshal(rr.Body.Bytes(), &apiKeyAuthReq)
assert.NoError(t, err)
assert.False(t, apiKeyAuthReq["allow_api_key_auth"])
_, err = httpdtest.RemoveAdmin(admin, http.StatusOK) _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
req, err = http.NewRequest(http.MethodGet, adminManageAPIKeyPath, nil) req, err = http.NewRequest(http.MethodGet, adminProfilePath, nil)
assert.NoError(t, err) assert.NoError(t, err)
setBearerForReq(req, token) setBearerForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusNotFound, rr) checkResponseCode(t, http.StatusNotFound, rr)
req, err = http.NewRequest(http.MethodPut, adminManageAPIKeyPath, bytes.NewBuffer(asJSON)) req, err = http.NewRequest(http.MethodPut, adminProfilePath, bytes.NewBuffer(asJSON))
assert.NoError(t, err) assert.NoError(t, err)
setBearerForReq(req, token) setBearerForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
@ -7473,13 +7554,13 @@ func TestWebAPILoginMock(t *testing.T) {
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr) checkResponseCode(t, http.StatusOK, rr)
// API token is not valid for web usage // API token is not valid for web usage
req, _ = http.NewRequest(http.MethodGet, webClientCredentialsPath, nil) req, _ = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
setJWTCookieForReq(req, apiToken) setJWTCookieForReq(req, apiToken)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusFound, rr) checkResponseCode(t, http.StatusFound, rr)
assert.Equal(t, webClientLoginPath, rr.Header().Get("Location")) assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
req, _ = http.NewRequest(http.MethodGet, webClientCredentialsPath, nil) req, _ = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
setJWTCookieForReq(req, webToken) setJWTCookieForReq(req, webToken)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr) checkResponseCode(t, http.StatusOK, rr)
@ -7509,13 +7590,13 @@ func TestWebClientLoginMock(t *testing.T) {
checkResponseCode(t, http.StatusFound, rr) checkResponseCode(t, http.StatusFound, rr)
assert.Equal(t, webLoginPath, rr.Header().Get("Location")) assert.Equal(t, webLoginPath, rr.Header().Get("Location"))
// bearer should not work // bearer should not work
req, _ = http.NewRequest(http.MethodGet, webClientCredentialsPath, nil) req, _ = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
setBearerForReq(req, webToken) setBearerForReq(req, webToken)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusFound, rr) checkResponseCode(t, http.StatusFound, rr)
assert.Equal(t, webClientLoginPath, rr.Header().Get("Location")) assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
// now try to render client pages // now try to render client pages
req, _ = http.NewRequest(http.MethodGet, webClientCredentialsPath, nil) req, _ = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
setJWTCookieForReq(req, webToken) setJWTCookieForReq(req, webToken)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr) checkResponseCode(t, http.StatusOK, rr)
@ -7529,7 +7610,7 @@ func TestWebClientLoginMock(t *testing.T) {
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusFound, rr) checkResponseCode(t, http.StatusFound, rr)
assert.Equal(t, webClientLoginPath, rr.Header().Get("Location")) assert.Equal(t, webClientLoginPath, rr.Header().Get("Location"))
req, _ = http.NewRequest(http.MethodGet, webClientCredentialsPath, nil) req, _ = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
setJWTCookieForReq(req, webToken) setJWTCookieForReq(req, webToken)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusFound, rr) checkResponseCode(t, http.StatusFound, rr)
@ -7545,7 +7626,7 @@ func TestWebClientLoginMock(t *testing.T) {
err = os.RemoveAll(user.GetHomeDir()) err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err) assert.NoError(t, err)
req, _ = http.NewRequest(http.MethodGet, webClientCredentialsPath, nil) req, _ = http.NewRequest(http.MethodGet, webClientProfilePath, nil)
setJWTCookieForReq(req, webToken) setJWTCookieForReq(req, webToken)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusInternalServerError, rr) checkResponseCode(t, http.StatusInternalServerError, rr)
@ -7602,7 +7683,7 @@ func TestWebClientLoginMock(t *testing.T) {
form := make(url.Values) form := make(url.Values)
form.Set("public_keys", testPubKey) form.Set("public_keys", testPubKey)
form.Set(csrfFormToken, csrfToken) form.Set(csrfFormToken, csrfToken)
req, _ = http.NewRequest(http.MethodPost, webChangeClientKeysPath, bytes.NewBuffer([]byte(form.Encode()))) req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, webToken) setJWTCookieForReq(req, webToken)
rr = executeRequest(req) rr = executeRequest(req)
@ -7864,15 +7945,22 @@ func TestWebClientChangePwd(t *testing.T) {
csrfToken, err := getCSRFToken(httpBaseURL + webClientLoginPath) csrfToken, err := getCSRFToken(httpBaseURL + webClientLoginPath)
assert.NoError(t, err) assert.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, webChangeClientPwdPath, nil)
assert.NoError(t, err)
setJWTCookieForReq(req, webToken)
rr := executeRequest(req)
checkResponseCode(t, http.StatusOK, rr)
form := make(url.Values) form := make(url.Values)
form.Set("current_password", defaultPassword) form.Set("current_password", defaultPassword)
form.Set("new_password1", defaultPassword) form.Set("new_password1", defaultPassword)
form.Set("new_password2", defaultPassword) form.Set("new_password2", defaultPassword)
// no csrf token // no csrf token
req, _ := http.NewRequest(http.MethodPost, webChangeClientPwdPath, bytes.NewBuffer([]byte(form.Encode()))) req, err = http.NewRequest(http.MethodPost, webChangeClientPwdPath, bytes.NewBuffer([]byte(form.Encode())))
assert.NoError(t, err)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, webToken) setJWTCookieForReq(req, webToken)
rr := executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusForbidden, rr) checkResponseCode(t, http.StatusForbidden, rr)
assert.Contains(t, rr.Body.String(), "unable to verify form token") assert.Contains(t, rr.Body.String(), "unable to verify form token")
@ -8004,64 +8092,6 @@ func TestWebAPIPublicKeys(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
} }
func TestWebClientChangePubKeys(t *testing.T) {
user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
assert.NoError(t, err)
webToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
assert.NoError(t, err)
csrfToken, err := getCSRFToken(httpBaseURL + webClientLoginPath)
assert.NoError(t, err)
form := make(url.Values)
form.Set("public_keys", testPubKey)
form.Add("public_keys", testPubKey1)
// no csrf token
req, _ := http.NewRequest(http.MethodPost, webChangeClientKeysPath, bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, webToken)
rr := executeRequest(req)
checkResponseCode(t, http.StatusForbidden, rr)
assert.Contains(t, rr.Body.String(), "unable to verify form token")
form.Set(csrfFormToken, csrfToken)
req, _ = http.NewRequest(http.MethodPost, webChangeClientKeysPath, bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, webToken)
rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr)
assert.Contains(t, rr.Body.String(), "Your public keys has been successfully updated")
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.Len(t, user.PublicKeys, 2)
form.Set("public_keys", "invalid")
req, _ = http.NewRequest(http.MethodPost, webChangeClientKeysPath, bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, webToken)
rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr)
assert.Contains(t, rr.Body.String(), "Validation error: could not parse key")
user.Filters.WebClient = append(user.Filters.WebClient, sdk.WebClientPubKeyChangeDisabled)
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
webToken, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
assert.NoError(t, err)
form.Set(csrfFormToken, csrfToken)
form.Set("public_keys", testPubKey)
req, _ = http.NewRequest(http.MethodPost, webChangeClientKeysPath, bytes.NewBuffer([]byte(form.Encode())))
req.RequestURI = webChangeClientKeysPath
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, webToken)
rr = executeRequest(req)
checkResponseCode(t, http.StatusForbidden, rr)
_, err = httpdtest.RemoveUser(user, http.StatusOK)
assert.NoError(t, err)
err = os.RemoveAll(user.GetHomeDir())
assert.NoError(t, err)
}
func TestPreDownloadHook(t *testing.T) { func TestPreDownloadHook(t *testing.T) {
if runtime.GOOS == osWindows { if runtime.GOOS == osWindows {
t.Skip("this test is not available on Windows") t.Skip("this test is not available on Windows")
@ -9743,7 +9773,7 @@ func TestWebAdminLoginMock(t *testing.T) {
} }
func TestAdminNoToken(t *testing.T) { func TestAdminNoToken(t *testing.T) {
req, _ := http.NewRequest(http.MethodGet, webAdminCredentialsPath, nil) req, _ := http.NewRequest(http.MethodGet, webAdminProfilePath, nil)
rr := executeRequest(req) rr := executeRequest(req)
checkResponseCode(t, http.StatusFound, rr) checkResponseCode(t, http.StatusFound, rr)
assert.Equal(t, webLoginPath, rr.Header().Get("Location")) assert.Equal(t, webLoginPath, rr.Header().Get("Location"))
@ -9762,10 +9792,8 @@ func TestAdminNoToken(t *testing.T) {
checkResponseCode(t, http.StatusUnauthorized, rr) checkResponseCode(t, http.StatusUnauthorized, rr)
} }
func TestWebUserAllowAPIKeyAuth(t *testing.T) { func TestWebUserProfile(t *testing.T) {
u := getTestUser() user, _, err := httpdtest.AddUser(getTestUser(), http.StatusCreated)
u.Filters.AllowAPIKeyAuth = true
user, _, err := httpdtest.AddUser(u, http.StatusCreated)
assert.NoError(t, err) assert.NoError(t, err)
csrfToken, err := getCSRFToken(httpBaseURL + webClientLoginPath) csrfToken, err := getCSRFToken(httpBaseURL + webClientLoginPath)
@ -9773,10 +9801,17 @@ func TestWebUserAllowAPIKeyAuth(t *testing.T) {
token, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword) token, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
assert.NoError(t, err) assert.NoError(t, err)
email := "user@user.com"
description := "User"
form := make(url.Values) form := make(url.Values)
form.Set("allow_api_key_auth", "1") form.Set("allow_api_key_auth", "1")
form.Set("email", email)
form.Set("description", description)
form.Set("public_keys", testPubKey)
form.Add("public_keys", testPubKey1)
// no csrf token // no csrf token
req, err := http.NewRequest(http.MethodPost, webChangeClientAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode()))) req, err := http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
assert.NoError(t, err) assert.NoError(t, err)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, token) setJWTCookieForReq(req, token)
@ -9785,43 +9820,109 @@ func TestWebUserAllowAPIKeyAuth(t *testing.T) {
assert.Contains(t, rr.Body.String(), "unable to verify form token") assert.Contains(t, rr.Body.String(), "unable to verify form token")
form.Set(csrfFormToken, csrfToken) form.Set(csrfFormToken, csrfToken)
req, _ = http.NewRequest(http.MethodPost, webChangeClientAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode()))) req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, token) setJWTCookieForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr) checkResponseCode(t, http.StatusOK, rr)
assert.Contains(t, rr.Body.String(), "API key authentication updated") assert.Contains(t, rr.Body.String(), "Your profile has been successfully updated")
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK) user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, user.Filters.AllowAPIKeyAuth) assert.True(t, user.Filters.AllowAPIKeyAuth)
assert.Len(t, user.PublicKeys, 2)
assert.Equal(t, email, user.Email)
assert.Equal(t, description, user.Description)
form = make(url.Values) // set an invalid email
form.Set(csrfFormToken, csrfToken) form.Set("email", "not an email")
req, _ = http.NewRequest(http.MethodPost, webChangeClientAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode()))) req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, token) setJWTCookieForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr) checkResponseCode(t, http.StatusOK, rr)
assert.Contains(t, rr.Body.String(), "API key authentication updated") assert.Contains(t, rr.Body.String(), "Validation error: email")
// invalid public key
form.Set("email", email)
form.Set("public_keys", "invalid")
req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, token)
rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr)
assert.Contains(t, rr.Body.String(), "Validation error: could not parse key")
// now remove permissions
form.Set("public_keys", testPubKey)
user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled}
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
token, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
assert.NoError(t, err)
form.Set("allow_api_key_auth", "0")
form.Set(csrfFormToken, csrfToken)
req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, token)
rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr)
assert.Contains(t, rr.Body.String(), "Your profile has been successfully updated")
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK) user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
assert.False(t, user.Filters.AllowAPIKeyAuth) assert.True(t, user.Filters.AllowAPIKeyAuth)
assert.Len(t, user.PublicKeys, 1)
assert.Equal(t, email, user.Email)
assert.Equal(t, description, user.Description)
user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled} user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientPubKeyChangeDisabled}
user, _, err = httpdtest.UpdateUser(user, http.StatusOK, "") _, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err) assert.NoError(t, err)
assert.False(t, user.CanChangeAPIKeyAuth()) token, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
newToken, err := getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
assert.NoError(t, err) assert.NoError(t, err)
form = make(url.Values) form.Set("public_keys", testPubKey)
form.Set("allow_api_key_auth", "1") form.Add("public_keys", testPubKey1)
form.Set(csrfFormToken, csrfToken) req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
req, _ = http.NewRequest(http.MethodPost, webChangeClientAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, newToken) setJWTCookieForReq(req, token)
rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr)
assert.Contains(t, rr.Body.String(), "Your profile has been successfully updated")
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.True(t, user.Filters.AllowAPIKeyAuth)
assert.Len(t, user.PublicKeys, 1)
assert.Equal(t, email, user.Email)
assert.Equal(t, description, user.Description)
user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientInfoChangeDisabled}
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
token, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
assert.NoError(t, err)
form.Set("email", "newemail@user.com")
form.Set("description", "new description")
req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, token)
rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr)
assert.Contains(t, rr.Body.String(), "Your profile has been successfully updated")
user, _, err = httpdtest.GetUserByUsername(user.Username, http.StatusOK)
assert.NoError(t, err)
assert.True(t, user.Filters.AllowAPIKeyAuth)
assert.Len(t, user.PublicKeys, 2)
assert.Equal(t, email, user.Email)
assert.Equal(t, description, user.Description)
// finally disable all profile permissions
user.Filters.WebClient = []string{sdk.WebClientAPIKeyAuthChangeDisabled, sdk.WebClientInfoChangeDisabled,
sdk.WebClientPubKeyChangeDisabled}
_, _, err = httpdtest.UpdateUser(user, http.StatusOK, "")
assert.NoError(t, err)
token, err = getJWTWebClientTokenFromTestServer(defaultUsername, defaultPassword)
assert.NoError(t, err)
req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusForbidden, rr) checkResponseCode(t, http.StatusForbidden, rr)
@ -9832,14 +9933,14 @@ func TestWebUserAllowAPIKeyAuth(t *testing.T) {
form = make(url.Values) form = make(url.Values)
form.Set(csrfFormToken, csrfToken) form.Set(csrfFormToken, csrfToken)
req, _ = http.NewRequest(http.MethodPost, webChangeClientAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode()))) req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, token) setJWTCookieForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusInternalServerError, rr) checkResponseCode(t, http.StatusInternalServerError, rr)
} }
func TestWebAdminAllowAPIKeyAuth(t *testing.T) { func TestWebAdminProfile(t *testing.T) {
admin := getTestAdmin() admin := getTestAdmin()
admin.Username = altAdminUsername admin.Username = altAdminUsername
admin.Password = altAdminPassword admin.Password = altAdminPassword
@ -9849,48 +9950,60 @@ func TestWebAdminAllowAPIKeyAuth(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath) csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
assert.NoError(t, err) assert.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, webAdminProfilePath, nil)
assert.NoError(t, err)
setJWTCookieForReq(req, token)
rr := executeRequest(req)
checkResponseCode(t, http.StatusOK, rr)
form := make(url.Values) form := make(url.Values)
form.Set("allow_api_key_auth", "1") form.Set("allow_api_key_auth", "1")
form.Set("email", "admin@example.com")
form.Set("description", "admin desc")
// no csrf token // no csrf token
req, err := http.NewRequest(http.MethodPost, webChangeAdminAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode()))) req, err = http.NewRequest(http.MethodPost, webAdminProfilePath, bytes.NewBuffer([]byte(form.Encode())))
assert.NoError(t, err) assert.NoError(t, err)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, token) setJWTCookieForReq(req, token)
rr := executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusForbidden, rr) checkResponseCode(t, http.StatusForbidden, rr)
assert.Contains(t, rr.Body.String(), "unable to verify form token") assert.Contains(t, rr.Body.String(), "unable to verify form token")
form.Set(csrfFormToken, csrfToken) form.Set(csrfFormToken, csrfToken)
req, _ = http.NewRequest(http.MethodPost, webChangeAdminAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode()))) req, _ = http.NewRequest(http.MethodPost, webAdminProfilePath, bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, token) setJWTCookieForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr) checkResponseCode(t, http.StatusOK, rr)
assert.Contains(t, rr.Body.String(), "API key authentication updated") assert.Contains(t, rr.Body.String(), "Your profile has been successfully updated")
admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK) admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
assert.True(t, admin.Filters.AllowAPIKeyAuth) assert.True(t, admin.Filters.AllowAPIKeyAuth)
assert.Equal(t, "admin@example.com", admin.Email)
assert.Equal(t, "admin desc", admin.Description)
form = make(url.Values) form = make(url.Values)
form.Set(csrfFormToken, csrfToken) form.Set(csrfFormToken, csrfToken)
req, _ = http.NewRequest(http.MethodPost, webChangeAdminAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode()))) req, _ = http.NewRequest(http.MethodPost, webAdminProfilePath, bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, token) setJWTCookieForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
checkResponseCode(t, http.StatusOK, rr) checkResponseCode(t, http.StatusOK, rr)
assert.Contains(t, rr.Body.String(), "API key authentication updated") assert.Contains(t, rr.Body.String(), "Your profile has been successfully updated")
admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK) admin, _, err = httpdtest.GetAdminByUsername(admin.Username, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
assert.False(t, admin.Filters.AllowAPIKeyAuth) assert.False(t, admin.Filters.AllowAPIKeyAuth)
assert.Empty(t, admin.Email)
assert.Empty(t, admin.Description)
_, err = httpdtest.RemoveAdmin(admin, http.StatusOK) _, err = httpdtest.RemoveAdmin(admin, http.StatusOK)
assert.NoError(t, err) assert.NoError(t, err)
form = make(url.Values) form = make(url.Values)
form.Set(csrfFormToken, csrfToken) form.Set(csrfFormToken, csrfToken)
req, _ = http.NewRequest(http.MethodPost, webChangeAdminAPIKeyAccessPath, bytes.NewBuffer([]byte(form.Encode()))) req, _ = http.NewRequest(http.MethodPost, webAdminProfilePath, bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
setJWTCookieForReq(req, token) setJWTCookieForReq(req, token)
rr = executeRequest(req) rr = executeRequest(req)
@ -9908,7 +10021,7 @@ func TestWebAdminPwdChange(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath) csrfToken, err := getCSRFToken(httpBaseURL + webLoginPath)
assert.NoError(t, err) assert.NoError(t, err)
req, err := http.NewRequest(http.MethodGet, webAdminCredentialsPath, nil) req, err := http.NewRequest(http.MethodGet, webChangeAdminPwdPath, nil)
assert.NoError(t, err) assert.NoError(t, err)
setJWTCookieForReq(req, token) setJWTCookieForReq(req, token)
rr := executeRequest(req) rr := executeRequest(req)

View file

@ -411,22 +411,22 @@ func TestInvalidToken(t *testing.T) {
assert.Contains(t, rr.Body.String(), "Invalid token claims") assert.Contains(t, rr.Body.String(), "Invalid token claims")
rr = httptest.NewRecorder() rr = httptest.NewRecorder()
getUserAPIKeyAuthStatus(rr, req) getUserProfile(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code) assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "Invalid token claims") assert.Contains(t, rr.Body.String(), "Invalid token claims")
rr = httptest.NewRecorder() rr = httptest.NewRecorder()
changeUserAPIKeyAuthStatus(rr, req) updateUserProfile(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code) assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "Invalid token claims") assert.Contains(t, rr.Body.String(), "Invalid token claims")
rr = httptest.NewRecorder() rr = httptest.NewRecorder()
getAdminAPIKeyAuthStatus(rr, req) getAdminProfile(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code) assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "Invalid token claims") assert.Contains(t, rr.Body.String(), "Invalid token claims")
rr = httptest.NewRecorder() rr = httptest.NewRecorder()
changeAdminAPIKeyAuthStatus(rr, req) updateAdminProfile(rr, req)
assert.Equal(t, http.StatusBadRequest, rr.Code) assert.Equal(t, http.StatusBadRequest, rr.Code)
assert.Contains(t, rr.Body.String(), "Invalid token claims") assert.Contains(t, rr.Body.String(), "Invalid token claims")
@ -577,9 +577,8 @@ func TestCreateTokenError(t *testing.T) {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rr = httptest.NewRecorder() rr = httptest.NewRecorder()
handleWebAdminChangePwdPost(rr, req) handleWebAdminChangePwdPost(rr, req)
// the claim is invalid so we fail to render the client page since assert.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
// we have to load the logged admin assert.Contains(t, rr.Body.String(), "invalid URL escape")
assert.Equal(t, http.StatusInternalServerError, rr.Code, rr.Body.String())
req, _ = http.NewRequest(http.MethodGet, webLoginPath+"?a=a%C3%A2%G3", nil) req, _ = http.NewRequest(http.MethodGet, webLoginPath+"?a=a%C3%A2%G3", nil)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
@ -596,24 +595,19 @@ func TestCreateTokenError(t *testing.T) {
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rr = httptest.NewRecorder() rr = httptest.NewRecorder()
handleWebClientChangePwdPost(rr, req) handleWebClientChangePwdPost(rr, req)
assert.Equal(t, http.StatusInternalServerError, rr.Code, rr.Body.String()) assert.Equal(t, http.StatusOK, rr.Code, rr.Body.String())
assert.Contains(t, rr.Body.String(), "invalid URL escape")
req, _ = http.NewRequest(http.MethodPost, webChangeClientKeysPath+"?a=a%C3%AO%GB", bytes.NewBuffer([]byte(form.Encode()))) req, _ = http.NewRequest(http.MethodPost, webClientProfilePath+"?a=a%C3%AO%GB", bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rr = httptest.NewRecorder() rr = httptest.NewRecorder()
handleWebClientManageKeysPost(rr, req) handleWebClientProfilePost(rr, req)
assert.Equal(t, http.StatusInternalServerError, rr.Code, rr.Body.String()) assert.Equal(t, http.StatusInternalServerError, rr.Code, rr.Body.String())
req, _ = http.NewRequest(http.MethodPost, webChangeClientAPIKeyAccessPath+"?a=a%C3%AO%GA", bytes.NewBuffer([]byte(form.Encode()))) req, _ = http.NewRequest(http.MethodPost, webAdminProfilePath+"?a=a%C3%AO%GB", bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rr = httptest.NewRecorder() rr = httptest.NewRecorder()
handleWebClientManageAPIKeyPost(rr, req) handleWebAdminProfilePost(rr, req)
assert.Equal(t, http.StatusInternalServerError, rr.Code, rr.Body.String())
req, _ = http.NewRequest(http.MethodPost, webChangeAdminAPIKeyAccessPath+"?a=a%C3%AO%GB", bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
rr = httptest.NewRecorder()
handleWebAdminManageAPIKeyPost(rr, req)
assert.Equal(t, http.StatusInternalServerError, rr.Code, rr.Body.String()) assert.Equal(t, http.StatusInternalServerError, rr.Code, rr.Body.String())
req, _ = http.NewRequest(http.MethodPost, webAdminTwoFactorPath+"?a=a%C3%AO%GC", bytes.NewBuffer([]byte(form.Encode()))) req, _ = http.NewRequest(http.MethodPost, webAdminTwoFactorPath+"?a=a%C3%AO%GC", bytes.NewBuffer([]byte(form.Encode())))
@ -754,8 +748,8 @@ func TestJWTTokenValidation(t *testing.T) {
permClientFn := checkHTTPUserPerm(sdk.WebClientPubKeyChangeDisabled) permClientFn := checkHTTPUserPerm(sdk.WebClientPubKeyChangeDisabled)
fn = permClientFn(r) fn = permClientFn(r)
rr = httptest.NewRecorder() rr = httptest.NewRecorder()
req, _ = http.NewRequest(http.MethodPost, webChangeClientKeysPath, nil) req, _ = http.NewRequest(http.MethodPost, webClientProfilePath, nil)
req.RequestURI = webChangeClientKeysPath req.RequestURI = webClientProfilePath
ctx = jwtauth.NewContext(req.Context(), token, errTest) ctx = jwtauth.NewContext(req.Context(), token, errTest)
fn.ServeHTTP(rr, req.WithContext(ctx)) fn.ServeHTTP(rr, req.WithContext(ctx))
assert.Equal(t, http.StatusBadRequest, rr.Code) assert.Equal(t, http.StatusBadRequest, rr.Code)
@ -1745,19 +1739,10 @@ func TestInvalidClaims(t *testing.T) {
form := make(url.Values) form := make(url.Values)
form.Set(csrfFormToken, createCSRFToken()) form.Set(csrfFormToken, createCSRFToken())
form.Set("public_keys", "") form.Set("public_keys", "")
req, _ := http.NewRequest(http.MethodPost, webChangeClientKeysPath, bytes.NewBuffer([]byte(form.Encode()))) req, _ := http.NewRequest(http.MethodPost, webClientProfilePath, bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"])) req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"]))
handleWebClientManageKeysPost(rr, req) handleWebClientProfilePost(rr, req)
assert.Equal(t, http.StatusInternalServerError, rr.Code)
form = make(url.Values)
form.Set(csrfFormToken, createCSRFToken())
form.Set("allow_api_key_auth", "")
req, _ = http.NewRequest(http.MethodPost, webChangeClientKeysPath, bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"]))
handleWebClientManageAPIKeyPost(rr, req)
assert.Equal(t, http.StatusInternalServerError, rr.Code) assert.Equal(t, http.StatusInternalServerError, rr.Code)
admin := dataprovider.Admin{ admin := dataprovider.Admin{
@ -1774,10 +1759,10 @@ func TestInvalidClaims(t *testing.T) {
form = make(url.Values) form = make(url.Values)
form.Set(csrfFormToken, createCSRFToken()) form.Set(csrfFormToken, createCSRFToken())
form.Set("allow_api_key_auth", "") form.Set("allow_api_key_auth", "")
req, _ = http.NewRequest(http.MethodPost, webChangeClientKeysPath, bytes.NewBuffer([]byte(form.Encode()))) req, _ = http.NewRequest(http.MethodPost, webAdminProfilePath, bytes.NewBuffer([]byte(form.Encode())))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"])) req.Header.Set("Cookie", fmt.Sprintf("jwt=%v", token["access_token"]))
handleWebAdminManageAPIKeyPost(rr, req) handleWebAdminProfilePost(rr, req)
assert.Equal(t, http.StatusInternalServerError, rr.Code) assert.Equal(t, http.StatusInternalServerError, rr.Code)
} }

View file

@ -243,25 +243,22 @@ paths:
$ref: '#/components/responses/InternalServerError' $ref: '#/components/responses/InternalServerError'
default: default:
$ref: '#/components/responses/DefaultResponse' $ref: '#/components/responses/DefaultResponse'
/admin/apikeyauth: /admin/profile:
get: get:
security: security:
- BearerAuth: [] - BearerAuth: []
tags: tags:
- admins - admins
summary: Get API key authentication status summary: Get profile
description: 'Returns the API Key authentication status for the logged in admin' description: 'Returns the profile for the logged in admin'
operationId: get_admin_api_key_status operationId: get_admin_profile
responses: responses:
'200': '200':
description: successful operation description: successful operation
content: content:
application/json: application/json:
schema: schema:
type: object $ref: '#/components/schemas/AdminProfile'
properties:
allow_api_key_auth:
type: boolean
'401': '401':
$ref: '#/components/responses/Unauthorized' $ref: '#/components/responses/Unauthorized'
'403': '403':
@ -275,18 +272,15 @@ paths:
- BearerAuth: [] - BearerAuth: []
tags: tags:
- admins - admins
summary: Update API key auth status summary: Update admin profile
description: 'Allows to enable/disable the API key authentication for the logged in admin. If enabled, you can impersonate this admin, in REST API, using an API key, otherwise your credentials, including two-factor authentication, if enabled, are required to use the REST API on your behalf' description: 'Allows to update the profile for the logged in admin'
operationId: update_admin_api_key_status operationId: update_admin_profile
requestBody: requestBody:
required: true required: true
content: content:
application/json: application/json:
schema: schema:
type: object $ref: '#/components/schemas/AdminProfile'
properties:
allow_api_key_auth:
type: boolean
responses: responses:
'200': '200':
description: successful operation description: successful operation
@ -2338,8 +2332,9 @@ paths:
- BearerAuth: [] - BearerAuth: []
tags: tags:
- users API - users API
deprecated: true
summary: Get the user's public keys summary: Get the user's public keys
description: Returns the public keys for the logged in user description: 'Returns the public keys for the logged in user. Deprecated please use "/user/profile" instead'
operationId: get_user_public_keys operationId: get_user_public_keys
responses: responses:
'200': '200':
@ -2365,8 +2360,9 @@ paths:
- BearerAuth: [] - BearerAuth: []
tags: tags:
- users API - users API
deprecated: true
summary: Set the user's public keys summary: Set the user's public keys
description: Sets the public keys for the logged in user. Public keys must be in OpenSSH format description: 'Sets the public keys for the logged in user. Public keys must be in OpenSSH format. Deprecated please use "/user/profile" instead'
operationId: set_user_public_keys operationId: set_user_public_keys
requestBody: requestBody:
required: true required: true
@ -2395,25 +2391,22 @@ paths:
$ref: '#/components/responses/InternalServerError' $ref: '#/components/responses/InternalServerError'
default: default:
$ref: '#/components/responses/DefaultResponse' $ref: '#/components/responses/DefaultResponse'
/user/apikeyauth: /user/profile:
get: get:
security: security:
- BearerAuth: [] - BearerAuth: []
tags: tags:
- users API - users API
summary: Get API key authentication status summary: Get user profile
description: 'Returns the API Key authentication status for the logged in user' description: 'Returns the profile for the logged in user'
operationId: get_user_api_key_status operationId: get_user_profile
responses: responses:
'200': '200':
description: successful operation description: successful operation
content: content:
application/json: application/json:
schema: schema:
type: object $ref: '#/components/schemas/UserProfile'
properties:
allow_api_key_auth:
type: boolean
'401': '401':
$ref: '#/components/responses/Unauthorized' $ref: '#/components/responses/Unauthorized'
'403': '403':
@ -2427,18 +2420,15 @@ paths:
- BearerAuth: [] - BearerAuth: []
tags: tags:
- users API - users API
summary: Update API key auth status summary: Update profile
description: 'Allows to enable/disable the API key authentication for the logged in user. If enabled, you can impersonate this user, in REST API, using an API key, otherwise your credentials, including two-factor authentication, if enabled, are required to use the REST API on your behalf' description: 'Allows to update the profile for the logged in user'
operationId: update_user_api_key_status operationId: update_user_profile
requestBody: requestBody:
required: true required: true
content: content:
application/json: application/json:
schema: schema:
type: object $ref: '#/components/schemas/UserProfile'
properties:
allow_api_key_auth:
type: boolean
responses: responses:
'200': '200':
description: successful operation description: successful operation
@ -3224,6 +3214,7 @@ components:
- mfa-disabled - mfa-disabled
- password-change-disabled - password-change-disabled
- api-key-auth-change-disabled - api-key-auth-change-disabled
- info-change-disabled
description: | description: |
Options: Options:
* `publickey-change-disabled` - changing SSH public keys is not allowed * `publickey-change-disabled` - changing SSH public keys is not allowed
@ -3231,6 +3222,7 @@ components:
* `mfa-disabled` - enabling multi-factor authentication is not allowed. This option cannot be set if the user has MFA already enabled * `mfa-disabled` - enabling multi-factor authentication is not allowed. This option cannot be set if the user has MFA already enabled
* `password-change-disabled` - changing password is not allowed * `password-change-disabled` - changing password is not allowed
* `api-key-auth-change-disabled` - enabling/disabling API key authentication is not allowed * `api-key-auth-change-disabled` - enabling/disabling API key authentication is not allowed
* `info-change-disabled` - changing info such as email and description is not allowed
APIKeyScope: APIKeyScope:
type: integer type: integer
enum: enum:
@ -3830,6 +3822,34 @@ components:
type: integer type: integer
format: int64 format: int64
description: Last user login as unix timestamp in milliseconds. It is saved at most once every 10 minutes description: Last user login as unix timestamp in milliseconds. It is saved at most once every 10 minutes
AdminProfile:
type: object
properties:
email:
type: string
format: email
description:
type: string
allow_api_key_auth:
type: boolean
description: 'If enabled, you can impersonate this admin, in REST API, using an API key. If disabled admin credentials are required for impersonation'
UserProfile:
type: object
properties:
email:
type: string
format: email
description:
type: string
allow_api_key_auth:
type: boolean
description: 'If enabled, you can impersonate this user, in REST API, using an API key. If disabled user credentials are required for impersonation'
public_keys:
type: array
items:
type: string
example: ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEUWwDwEWhTbF0MqAsp/oXK1HR2cElhM8oo1uVmL3ZeDKDiTm4ljMr92wfTgIGDqIoxmVqgYIkAOAhuykAVWBzc= user@host
description: Public keys in OpenSSH format
APIKey: APIKey:
type: object type: object
properties: properties:

View file

@ -909,8 +909,8 @@ func (s *httpdServer) initializeRouter() {
}) })
router.With(forbidAPIKeyAuthentication).Get(logoutPath, s.logout) router.With(forbidAPIKeyAuthentication).Get(logoutPath, s.logout)
router.With(forbidAPIKeyAuthentication).Get(adminManageAPIKeyPath, getAdminAPIKeyAuthStatus) router.With(forbidAPIKeyAuthentication).Get(adminProfilePath, getAdminProfile)
router.With(forbidAPIKeyAuthentication).Put(adminManageAPIKeyPath, changeAdminAPIKeyAuthStatus) router.With(forbidAPIKeyAuthentication).Put(adminProfilePath, updateAdminProfile)
router.With(forbidAPIKeyAuthentication).Put(adminPwdPath, changeAdminPassword) router.With(forbidAPIKeyAuthentication).Put(adminPwdPath, changeAdminPassword)
// compatibility layer to remove in v2.2 // compatibility layer to remove in v2.2
router.With(forbidAPIKeyAuthentication).Put(adminPwdCompatPath, changeAdminPassword) router.With(forbidAPIKeyAuthentication).Put(adminPwdCompatPath, changeAdminPassword)
@ -1003,9 +1003,8 @@ func (s *httpdServer) initializeRouter() {
Get(userPublicKeysPath, getUserPublicKeys) Get(userPublicKeysPath, getUserPublicKeys)
router.With(forbidAPIKeyAuthentication, checkHTTPUserPerm(sdk.WebClientPubKeyChangeDisabled)). router.With(forbidAPIKeyAuthentication, checkHTTPUserPerm(sdk.WebClientPubKeyChangeDisabled)).
Put(userPublicKeysPath, setUserPublicKeys) Put(userPublicKeysPath, setUserPublicKeys)
router.With(forbidAPIKeyAuthentication).Get(userManageAPIKeyPath, getUserAPIKeyAuthStatus) router.With(forbidAPIKeyAuthentication).Get(userProfilePath, getUserProfile)
router.With(forbidAPIKeyAuthentication, checkHTTPUserPerm(sdk.WebClientAPIKeyAuthChangeDisabled)). router.With(forbidAPIKeyAuthentication).Put(userProfilePath, updateUserProfile)
Put(userManageAPIKeyPath, changeUserAPIKeyAuthStatus)
// user TOTP APIs // user TOTP APIs
router.With(forbidAPIKeyAuthentication, checkHTTPUserPerm(sdk.WebClientMFADisabled)). router.With(forbidAPIKeyAuthentication, checkHTTPUserPerm(sdk.WebClientMFADisabled)).
Get(userTOTPConfigsPath, getTOTPConfigs) Get(userTOTPConfigsPath, getTOTPConfigs)
@ -1101,13 +1100,12 @@ func (s *httpdServer) initializeRouter() {
router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader). router.With(checkHTTPUserPerm(sdk.WebClientWriteDisabled), verifyCSRFHeader).
Delete(webClientDirsPath, deleteUserDir) Delete(webClientDirsPath, deleteUserDir)
router.With(s.refreshCookie).Get(webClientDownloadZipPath, handleWebClientDownloadZip) router.With(s.refreshCookie).Get(webClientDownloadZipPath, handleWebClientDownloadZip)
router.With(s.refreshCookie).Get(webClientCredentialsPath, handleClientGetCredentials) router.With(s.refreshCookie).Get(webClientProfilePath, handleClientGetProfile)
router.Post(webClientProfilePath, handleWebClientProfilePost)
router.With(checkHTTPUserPerm(sdk.WebClientPasswordChangeDisabled)).
Get(webChangeClientPwdPath, handleWebClientChangePwd)
router.With(checkHTTPUserPerm(sdk.WebClientPasswordChangeDisabled)). router.With(checkHTTPUserPerm(sdk.WebClientPasswordChangeDisabled)).
Post(webChangeClientPwdPath, handleWebClientChangePwdPost) Post(webChangeClientPwdPath, handleWebClientChangePwdPost)
router.With(checkHTTPUserPerm(sdk.WebClientAPIKeyAuthChangeDisabled)).
Post(webChangeClientAPIKeyAccessPath, handleWebClientManageAPIKeyPost)
router.With(checkHTTPUserPerm(sdk.WebClientPubKeyChangeDisabled)).
Post(webChangeClientKeysPath, handleWebClientManageKeysPost)
router.With(checkHTTPUserPerm(sdk.WebClientMFADisabled), s.refreshCookie). router.With(checkHTTPUserPerm(sdk.WebClientMFADisabled), s.refreshCookie).
Get(webClientMFAPath, handleWebClientMFA) Get(webClientMFAPath, handleWebClientMFA)
router.With(checkHTTPUserPerm(sdk.WebClientMFADisabled), verifyCSRFHeader). router.With(checkHTTPUserPerm(sdk.WebClientMFADisabled), verifyCSRFHeader).
@ -1150,9 +1148,11 @@ func (s *httpdServer) initializeRouter() {
router.Use(jwtAuthenticatorWebAdmin) router.Use(jwtAuthenticatorWebAdmin)
router.Get(webLogoutPath, handleWebLogout) router.Get(webLogoutPath, handleWebLogout)
router.With(s.refreshCookie).Get(webAdminCredentialsPath, handleWebAdminCredentials) router.With(s.refreshCookie).Get(webAdminProfilePath, handleWebAdminProfile)
router.Post(webAdminProfilePath, handleWebAdminProfilePost)
router.With(s.refreshCookie).Get(webChangeAdminPwdPath, handleWebAdminChangePwd)
router.Post(webChangeAdminPwdPath, handleWebAdminChangePwdPost) router.Post(webChangeAdminPwdPath, handleWebAdminChangePwdPost)
router.Post(webChangeAdminAPIKeyAccessPath, handleWebAdminManageAPIKeyPost)
router.With(s.refreshCookie).Get(webAdminMFAPath, handleWebAdminMFA) router.With(s.refreshCookie).Get(webAdminMFAPath, handleWebAdminMFA)
router.With(verifyCSRFHeader).Post(webAdminTOTPGeneratePath, generateTOTPSecret) router.With(verifyCSRFHeader).Post(webAdminTOTPGeneratePath, generateTOTPSecret)
router.With(verifyCSRFHeader).Post(webAdminTOTPValidatePath, validateTOTPPasscode) router.With(verifyCSRFHeader).Post(webAdminTOTPValidatePath, validateTOTPPasscode)

View file

@ -56,7 +56,8 @@ const (
templateStatus = "status.html" templateStatus = "status.html"
templateLogin = "login.html" templateLogin = "login.html"
templateDefender = "defender.html" templateDefender = "defender.html"
templateCredentials = "credentials.html" templateProfile = "profile.html"
templateChangePwd = "changepassword.html"
templateMaintenance = "maintenance.html" templateMaintenance = "maintenance.html"
templateMFA = "mfa.html" templateMFA = "mfa.html"
templateSetup = "adminsetup.html" templateSetup = "adminsetup.html"
@ -65,7 +66,8 @@ const (
pageConnectionsTitle = "Connections" pageConnectionsTitle = "Connections"
pageStatusTitle = "Status" pageStatusTitle = "Status"
pageFoldersTitle = "Folders" pageFoldersTitle = "Folders"
pageCredentialsTitle = "Manage credentials" pageProfileTitle = "My profile"
pageChangePwdTitle = "Change password"
pageMaintenanceTitle = "Maintenance" pageMaintenanceTitle = "Maintenance"
pageDefenderTitle = "Defender" pageDefenderTitle = "Defender"
pageSetupTitle = "Create first admin user" pageSetupTitle = "Create first admin user"
@ -91,7 +93,8 @@ type basePage struct {
FolderTemplateURL string FolderTemplateURL string
DefenderURL string DefenderURL string
LogoutURL string LogoutURL string
CredentialsURL string ProfileURL string
ChangePwdURL string
MFAURL string MFAURL string
FolderQuotaScanURL string FolderQuotaScanURL string
StatusURL string StatusURL string
@ -157,13 +160,17 @@ type adminPage struct {
IsAdd bool IsAdd bool
} }
type credentialsPage struct { type profilePage struct {
basePage basePage
Error string Error string
AllowAPIKeyAuth bool AllowAPIKeyAuth bool
ChangePwdURL string Email string
ManageAPIKeyURL string Description string
APIKeyError string }
type changePasswordPage struct {
basePage
Error string
} }
type mfaPage struct { type mfaPage struct {
@ -231,9 +238,13 @@ func loadAdminTemplates(templatesPath string) {
filepath.Join(templatesPath, templateAdminDir, templateBase), filepath.Join(templatesPath, templateAdminDir, templateBase),
filepath.Join(templatesPath, templateAdminDir, templateAdmin), filepath.Join(templatesPath, templateAdminDir, templateAdmin),
} }
credentialsPaths := []string{ profilePaths := []string{
filepath.Join(templatesPath, templateAdminDir, templateBase), filepath.Join(templatesPath, templateAdminDir, templateBase),
filepath.Join(templatesPath, templateAdminDir, templateCredentials), filepath.Join(templatesPath, templateAdminDir, templateProfile),
}
changePwdPaths := []string{
filepath.Join(templatesPath, templateAdminDir, templateBase),
filepath.Join(templatesPath, templateAdminDir, templateChangePwd),
} }
connectionsPaths := []string{ connectionsPaths := []string{
filepath.Join(templatesPath, templateAdminDir, templateBase), filepath.Join(templatesPath, templateAdminDir, templateBase),
@ -298,7 +309,8 @@ func loadAdminTemplates(templatesPath string) {
folderTmpl := util.LoadTemplate(rootTpl, folderPath...) folderTmpl := util.LoadTemplate(rootTpl, folderPath...)
statusTmpl := util.LoadTemplate(rootTpl, statusPath...) statusTmpl := util.LoadTemplate(rootTpl, statusPath...)
loginTmpl := util.LoadTemplate(rootTpl, loginPath...) loginTmpl := util.LoadTemplate(rootTpl, loginPath...)
credentialsTmpl := util.LoadTemplate(rootTpl, credentialsPaths...) profileTmpl := util.LoadTemplate(rootTpl, profilePaths...)
changePwdTmpl := util.LoadTemplate(rootTpl, changePwdPaths...)
maintenanceTmpl := util.LoadTemplate(rootTpl, maintenancePath...) maintenanceTmpl := util.LoadTemplate(rootTpl, maintenancePath...)
defenderTmpl := util.LoadTemplate(rootTpl, defenderPath...) defenderTmpl := util.LoadTemplate(rootTpl, defenderPath...)
mfaTmpl := util.LoadTemplate(nil, mfaPath...) mfaTmpl := util.LoadTemplate(nil, mfaPath...)
@ -316,7 +328,8 @@ func loadAdminTemplates(templatesPath string) {
adminTemplates[templateFolder] = folderTmpl adminTemplates[templateFolder] = folderTmpl
adminTemplates[templateStatus] = statusTmpl adminTemplates[templateStatus] = statusTmpl
adminTemplates[templateLogin] = loginTmpl adminTemplates[templateLogin] = loginTmpl
adminTemplates[templateCredentials] = credentialsTmpl adminTemplates[templateProfile] = profileTmpl
adminTemplates[templateChangePwd] = changePwdTmpl
adminTemplates[templateMaintenance] = maintenanceTmpl adminTemplates[templateMaintenance] = maintenanceTmpl
adminTemplates[templateDefender] = defenderTmpl adminTemplates[templateDefender] = defenderTmpl
adminTemplates[templateMFA] = mfaTmpl adminTemplates[templateMFA] = mfaTmpl
@ -343,7 +356,8 @@ func getBasePageData(title, currentURL string, r *http.Request) basePage {
FolderTemplateURL: webTemplateFolder, FolderTemplateURL: webTemplateFolder,
DefenderURL: webDefenderPath, DefenderURL: webDefenderPath,
LogoutURL: webLogoutPath, LogoutURL: webLogoutPath,
CredentialsURL: webAdminCredentialsPath, ProfileURL: webAdminProfilePath,
ChangePwdURL: webChangeAdminPwdPath,
MFAURL: webAdminMFAPath, MFAURL: webAdminMFAPath,
QuotaScanURL: webQuotaScanPath, QuotaScanURL: webQuotaScanPath,
ConnectionsURL: webConnectionsPath, ConnectionsURL: webConnectionsPath,
@ -446,13 +460,10 @@ func renderMFAPage(w http.ResponseWriter, r *http.Request) {
renderAdminTemplate(w, templateMFA, data) renderAdminTemplate(w, templateMFA, data)
} }
func renderCredentialsPage(w http.ResponseWriter, r *http.Request, pwdError, apiKeyError string) { func renderProfilePage(w http.ResponseWriter, r *http.Request, error string) {
data := credentialsPage{ data := profilePage{
basePage: getBasePageData(pageCredentialsTitle, webAdminCredentialsPath, r), basePage: getBasePageData(pageProfileTitle, webAdminProfilePath, r),
ChangePwdURL: webChangeAdminPwdPath, Error: error,
ManageAPIKeyURL: webChangeAdminAPIKeyAccessPath,
Error: pwdError,
APIKeyError: apiKeyError,
} }
admin, err := dataprovider.AdminExists(data.LoggedAdmin.Username) admin, err := dataprovider.AdminExists(data.LoggedAdmin.Username)
if err != nil { if err != nil {
@ -460,8 +471,19 @@ func renderCredentialsPage(w http.ResponseWriter, r *http.Request, pwdError, api
return return
} }
data.AllowAPIKeyAuth = admin.Filters.AllowAPIKeyAuth data.AllowAPIKeyAuth = admin.Filters.AllowAPIKeyAuth
data.Email = admin.Email
data.Description = admin.Description
renderAdminTemplate(w, templateCredentials, data) renderAdminTemplate(w, templateProfile, data)
}
func renderChangePasswordPage(w http.ResponseWriter, r *http.Request, error string) {
data := changePasswordPage{
basePage: getBasePageData(pageChangePwdTitle, webChangeAdminPwdPath, r),
Error: error,
}
renderAdminTemplate(w, templateChangePwd, data)
} }
func renderMaintenancePage(w http.ResponseWriter, r *http.Request, error string) { func renderMaintenancePage(w http.ResponseWriter, r *http.Request, error string) {
@ -1125,16 +1147,21 @@ func handleWebAdminMFA(w http.ResponseWriter, r *http.Request) {
renderMFAPage(w, r) renderMFAPage(w, r)
} }
func handleWebAdminCredentials(w http.ResponseWriter, r *http.Request) { func handleWebAdminProfile(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
renderCredentialsPage(w, r, "", "") renderProfilePage(w, r, "")
}
func handleWebAdminChangePwd(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
renderChangePasswordPage(w, r, "")
} }
func handleWebAdminChangePwdPost(w http.ResponseWriter, r *http.Request) { func handleWebAdminChangePwdPost(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
renderCredentialsPage(w, r, err.Error(), "") renderChangePasswordPage(w, r, err.Error())
return return
} }
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
@ -1144,17 +1171,17 @@ func handleWebAdminChangePwdPost(w http.ResponseWriter, r *http.Request) {
err = doChangeAdminPassword(r, r.Form.Get("current_password"), r.Form.Get("new_password1"), err = doChangeAdminPassword(r, r.Form.Get("current_password"), r.Form.Get("new_password1"),
r.Form.Get("new_password2")) r.Form.Get("new_password2"))
if err != nil { if err != nil {
renderCredentialsPage(w, r, err.Error(), "") renderChangePasswordPage(w, r, err.Error())
return return
} }
handleWebLogout(w, r) handleWebLogout(w, r)
} }
func handleWebAdminManageAPIKeyPost(w http.ResponseWriter, r *http.Request) { func handleWebAdminProfilePost(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
renderCredentialsPage(w, r, err.Error(), "") renderProfilePage(w, r, err.Error())
return return
} }
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
@ -1163,22 +1190,24 @@ func handleWebAdminManageAPIKeyPost(w http.ResponseWriter, r *http.Request) {
} }
claims, err := getTokenClaims(r) claims, err := getTokenClaims(r)
if err != nil || claims.Username == "" { if err != nil || claims.Username == "" {
renderCredentialsPage(w, r, "", "Invalid token claims") renderProfilePage(w, r, "Invalid token claims")
return return
} }
admin, err := dataprovider.AdminExists(claims.Username) admin, err := dataprovider.AdminExists(claims.Username)
if err != nil { if err != nil {
renderCredentialsPage(w, r, "", err.Error()) renderProfilePage(w, r, err.Error())
return return
} }
admin.Filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0 admin.Filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0
admin.Email = r.Form.Get("email")
admin.Description = r.Form.Get("description")
err = dataprovider.UpdateAdmin(&admin) err = dataprovider.UpdateAdmin(&admin)
if err != nil { if err != nil {
renderCredentialsPage(w, r, "", err.Error()) renderProfilePage(w, r, err.Error())
return return
} }
renderMessagePage(w, r, "API key authentication updated", "", http.StatusOK, nil, renderMessagePage(w, r, "Profile updated", "", http.StatusOK, nil,
"Your API key access permission has been successfully updated") "Your profile has been successfully updated")
} }
func handleWebLogout(w http.ResponseWriter, r *http.Request) { func handleWebLogout(w http.ResponseWriter, r *http.Request) {

View file

@ -30,12 +30,15 @@ const (
templateClientLogin = "login.html" templateClientLogin = "login.html"
templateClientFiles = "files.html" templateClientFiles = "files.html"
templateClientMessage = "message.html" templateClientMessage = "message.html"
templateClientCredentials = "credentials.html" templateClientProfile = "profile.html"
templateClientChangePwd = "changepassword.html"
templateClientTwoFactor = "twofactor.html" templateClientTwoFactor = "twofactor.html"
templateClientTwoFactorRecovery = "twofactor-recovery.html" templateClientTwoFactorRecovery = "twofactor-recovery.html"
templateClientMFA = "mfa.html" templateClientMFA = "mfa.html"
pageClientFilesTitle = "My Files" pageClientFilesTitle = "My Files"
pageClientCredentialsTitle = "Credentials" pageClientProfileTitle = "My Profile"
pageClientChangePwdTitle = "Change password"
pageClient2FATitle = "Two-factor auth"
) )
// condResult is the result of an HTTP request precondition check. // condResult is the result of an HTTP request precondition check.
@ -59,19 +62,20 @@ func isZeroTime(t time.Time) bool {
} }
type baseClientPage struct { type baseClientPage struct {
Title string Title string
CurrentURL string CurrentURL string
FilesURL string FilesURL string
CredentialsURL string ProfileURL string
StaticURL string ChangePwdURL string
LogoutURL string StaticURL string
MFAURL string LogoutURL string
MFATitle string MFAURL string
FilesTitle string MFATitle string
CredentialsTitle string FilesTitle string
Version string ProfileTitle string
CSRFToken string Version string
LoggedUser *dataprovider.User CSRFToken string
LoggedUser *dataprovider.User
} }
type dirMapping struct { type dirMapping struct {
@ -99,16 +103,19 @@ type clientMessagePage struct {
Success string Success string
} }
type clientCredentialsPage struct { type clientProfilePage struct {
baseClientPage baseClientPage
PublicKeys []string PublicKeys []string
CanSubmit bool
AllowAPIKeyAuth bool AllowAPIKeyAuth bool
ChangePwdURL string Email string
ManageKeysURL string Description string
ManageAPIKeyURL string Error string
PwdError string }
KeyError string
APIKeyError string type changeClientPasswordPage struct {
baseClientPage
Error string
} }
type clientMFAPage struct { type clientMFAPage struct {
@ -138,9 +145,13 @@ func loadClientTemplates(templatesPath string) {
filepath.Join(templatesPath, templateClientDir, templateClientBase), filepath.Join(templatesPath, templateClientDir, templateClientBase),
filepath.Join(templatesPath, templateClientDir, templateClientFiles), filepath.Join(templatesPath, templateClientDir, templateClientFiles),
} }
credentialsPaths := []string{ profilePaths := []string{
filepath.Join(templatesPath, templateClientDir, templateClientBase), filepath.Join(templatesPath, templateClientDir, templateClientBase),
filepath.Join(templatesPath, templateClientDir, templateClientCredentials), filepath.Join(templatesPath, templateClientDir, templateClientProfile),
}
changePwdPaths := []string{
filepath.Join(templatesPath, templateClientDir, templateClientBase),
filepath.Join(templatesPath, templateClientDir, templateClientChangePwd),
} }
loginPath := []string{ loginPath := []string{
filepath.Join(templatesPath, templateClientDir, templateClientBaseLogin), filepath.Join(templatesPath, templateClientDir, templateClientBaseLogin),
@ -164,7 +175,8 @@ func loadClientTemplates(templatesPath string) {
} }
filesTmpl := util.LoadTemplate(nil, filesPaths...) filesTmpl := util.LoadTemplate(nil, filesPaths...)
credentialsTmpl := util.LoadTemplate(nil, credentialsPaths...) profileTmpl := util.LoadTemplate(nil, profilePaths...)
changePwdTmpl := util.LoadTemplate(nil, changePwdPaths...)
loginTmpl := util.LoadTemplate(nil, loginPath...) loginTmpl := util.LoadTemplate(nil, loginPath...)
messageTmpl := util.LoadTemplate(nil, messagePath...) messageTmpl := util.LoadTemplate(nil, messagePath...)
mfaTmpl := util.LoadTemplate(nil, mfaPath...) mfaTmpl := util.LoadTemplate(nil, mfaPath...)
@ -172,7 +184,8 @@ func loadClientTemplates(templatesPath string) {
twoFactorRecoveryTmpl := util.LoadTemplate(nil, twoFactorRecoveryPath...) twoFactorRecoveryTmpl := util.LoadTemplate(nil, twoFactorRecoveryPath...)
clientTemplates[templateClientFiles] = filesTmpl clientTemplates[templateClientFiles] = filesTmpl
clientTemplates[templateClientCredentials] = credentialsTmpl clientTemplates[templateClientProfile] = profileTmpl
clientTemplates[templateClientChangePwd] = changePwdTmpl
clientTemplates[templateClientLogin] = loginTmpl clientTemplates[templateClientLogin] = loginTmpl
clientTemplates[templateClientMessage] = messageTmpl clientTemplates[templateClientMessage] = messageTmpl
clientTemplates[templateClientMFA] = mfaTmpl clientTemplates[templateClientMFA] = mfaTmpl
@ -188,19 +201,20 @@ func getBaseClientPageData(title, currentURL string, r *http.Request) baseClient
v := version.Get() v := version.Get()
return baseClientPage{ return baseClientPage{
Title: title, Title: title,
CurrentURL: currentURL, CurrentURL: currentURL,
FilesURL: webClientFilesPath, FilesURL: webClientFilesPath,
CredentialsURL: webClientCredentialsPath, ProfileURL: webClientProfilePath,
StaticURL: webStaticFilesPath, ChangePwdURL: webChangeClientPwdPath,
LogoutURL: webClientLogoutPath, StaticURL: webStaticFilesPath,
MFAURL: webClientMFAPath, LogoutURL: webClientLogoutPath,
MFATitle: "Two-factor auth", MFAURL: webClientMFAPath,
FilesTitle: pageClientFilesTitle, MFATitle: pageClient2FATitle,
CredentialsTitle: pageClientCredentialsTitle, FilesTitle: pageClientFilesTitle,
Version: fmt.Sprintf("%v-%v", v.Version, v.CommitHash), ProfileTitle: pageClientProfileTitle,
CSRFToken: csrfToken, Version: fmt.Sprintf("%v-%v", v.Version, v.CommitHash),
LoggedUser: getUserFromToken(r), CSRFToken: csrfToken,
LoggedUser: getUserFromToken(r),
} }
} }
@ -320,15 +334,10 @@ func renderFilesPage(w http.ResponseWriter, r *http.Request, dirName, error stri
renderClientTemplate(w, templateClientFiles, data) renderClientTemplate(w, templateClientFiles, data)
} }
func renderClientCredentialsPage(w http.ResponseWriter, r *http.Request, pwdError, keyError, apiKeyError string) { func renderClientProfilePage(w http.ResponseWriter, r *http.Request, error string) {
data := clientCredentialsPage{ data := clientProfilePage{
baseClientPage: getBaseClientPageData(pageClientCredentialsTitle, webClientCredentialsPath, r), baseClientPage: getBaseClientPageData(pageClientProfileTitle, webClientProfilePath, r),
ChangePwdURL: webChangeClientPwdPath, Error: error,
ManageKeysURL: webChangeClientKeysPath,
ManageAPIKeyURL: webChangeClientAPIKeyAccessPath,
PwdError: pwdError,
KeyError: keyError,
APIKeyError: apiKeyError,
} }
user, err := dataprovider.UserExists(data.LoggedUser.Username) user, err := dataprovider.UserExists(data.LoggedUser.Username)
if err != nil { if err != nil {
@ -337,7 +346,19 @@ func renderClientCredentialsPage(w http.ResponseWriter, r *http.Request, pwdErro
} }
data.PublicKeys = user.PublicKeys data.PublicKeys = user.PublicKeys
data.AllowAPIKeyAuth = user.Filters.AllowAPIKeyAuth data.AllowAPIKeyAuth = user.Filters.AllowAPIKeyAuth
renderClientTemplate(w, templateClientCredentials, data) data.Email = user.Email
data.Description = user.Description
data.CanSubmit = user.CanChangeAPIKeyAuth() || user.CanManagePublicKeys() || user.CanChangeInfo()
renderClientTemplate(w, templateClientProfile, data)
}
func renderClientChangePasswordPage(w http.ResponseWriter, r *http.Request, error string) {
data := changeClientPasswordPage{
baseClientPage: getBaseClientPageData(pageClientChangePwdTitle, webChangeClientPwdPath, r),
Error: error,
}
renderClientTemplate(w, templateClientChangePwd, data)
} }
func handleWebClientLogout(w http.ResponseWriter, r *http.Request) { func handleWebClientLogout(w http.ResponseWriter, r *http.Request) {
@ -513,16 +534,21 @@ func handleClientGetFiles(w http.ResponseWriter, r *http.Request) {
} }
} }
func handleClientGetCredentials(w http.ResponseWriter, r *http.Request) { func handleClientGetProfile(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
renderClientCredentialsPage(w, r, "", "", "") renderClientProfilePage(w, r, "")
}
func handleWebClientChangePwd(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
renderClientChangePasswordPage(w, r, "")
} }
func handleWebClientChangePwdPost(w http.ResponseWriter, r *http.Request) { func handleWebClientChangePwdPost(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
renderClientCredentialsPage(w, r, err.Error(), "", "") renderClientChangePasswordPage(w, r, err.Error())
return return
} }
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
@ -532,17 +558,17 @@ func handleWebClientChangePwdPost(w http.ResponseWriter, r *http.Request) {
err = doChangeUserPassword(r, r.Form.Get("current_password"), r.Form.Get("new_password1"), err = doChangeUserPassword(r, r.Form.Get("current_password"), r.Form.Get("new_password1"),
r.Form.Get("new_password2")) r.Form.Get("new_password2"))
if err != nil { if err != nil {
renderClientCredentialsPage(w, r, err.Error(), "", "") renderClientChangePasswordPage(w, r, err.Error())
return return
} }
handleWebClientLogout(w, r) handleWebClientLogout(w, r)
} }
func handleWebClientManageKeysPost(w http.ResponseWriter, r *http.Request) { func handleWebClientProfilePost(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize) r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
err := r.ParseForm() err := r.ParseForm()
if err != nil { if err != nil {
renderClientCredentialsPage(w, r, "", err.Error(), "") renderClientProfilePage(w, r, err.Error())
return return
} }
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil { if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
@ -551,53 +577,35 @@ func handleWebClientManageKeysPost(w http.ResponseWriter, r *http.Request) {
} }
claims, err := getTokenClaims(r) claims, err := getTokenClaims(r)
if err != nil || claims.Username == "" { if err != nil || claims.Username == "" {
renderClientCredentialsPage(w, r, "", "Invalid token claims", "") renderClientProfilePage(w, r, "Invalid token claims")
return return
} }
user, err := dataprovider.UserExists(claims.Username) user, err := dataprovider.UserExists(claims.Username)
if err != nil { if err != nil {
renderClientCredentialsPage(w, r, "", err.Error(), "") renderClientProfilePage(w, r, err.Error())
return return
} }
user.PublicKeys = r.Form["public_keys"] if !user.CanManagePublicKeys() && !user.CanChangeAPIKeyAuth() && !user.CanChangeInfo() {
renderClientForbiddenPage(w, r, "You are not allowed to change anything")
return
}
if user.CanManagePublicKeys() {
user.PublicKeys = r.Form["public_keys"]
}
if user.CanChangeAPIKeyAuth() {
user.Filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0
}
if user.CanChangeInfo() {
user.Email = r.Form.Get("email")
user.Description = r.Form.Get("description")
}
err = dataprovider.UpdateUser(&user) err = dataprovider.UpdateUser(&user)
if err != nil { if err != nil {
renderClientCredentialsPage(w, r, "", err.Error(), "") renderClientProfilePage(w, r, err.Error())
return return
} }
renderClientMessagePage(w, r, "Public keys updated", "", http.StatusOK, nil, renderClientMessagePage(w, r, "Profile updated", "", http.StatusOK, nil,
"Your public keys has been successfully updated") "Your profile has been successfully updated")
}
func handleWebClientManageAPIKeyPost(w http.ResponseWriter, r *http.Request) {
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
err := r.ParseForm()
if err != nil {
renderClientCredentialsPage(w, r, "", "", err.Error())
return
}
if err := verifyCSRFToken(r.Form.Get(csrfFormToken)); err != nil {
renderClientForbiddenPage(w, r, err.Error())
return
}
claims, err := getTokenClaims(r)
if err != nil || claims.Username == "" {
renderClientCredentialsPage(w, r, "", "", "Invalid token claims")
return
}
user, err := dataprovider.UserExists(claims.Username)
if err != nil {
renderClientCredentialsPage(w, r, "", "", err.Error())
return
}
user.Filters.AllowAPIKeyAuth = len(r.Form.Get("allow_api_key_auth")) > 0
err = dataprovider.UpdateUser(&user)
if err != nil {
renderClientCredentialsPage(w, r, "", "", err.Error())
return
}
renderClientMessagePage(w, r, "API key authentication updated", "", http.StatusOK, nil,
"Your API key access permission has been successfully updated")
} }
func handleWebClientMFA(w http.ResponseWriter, r *http.Request) { func handleWebClientMFA(w http.ResponseWriter, r *http.Request) {

View file

@ -14,12 +14,13 @@ const (
WebClientMFADisabled = "mfa-disabled" WebClientMFADisabled = "mfa-disabled"
WebClientPasswordChangeDisabled = "password-change-disabled" WebClientPasswordChangeDisabled = "password-change-disabled"
WebClientAPIKeyAuthChangeDisabled = "api-key-auth-change-disabled" WebClientAPIKeyAuthChangeDisabled = "api-key-auth-change-disabled"
WebClientInfoChangeDisabled = "info-change-disabled"
) )
var ( var (
// WebClientOptions defines the available options for the web client interface/user REST API // WebClientOptions defines the available options for the web client interface/user REST API
WebClientOptions = []string{WebClientPubKeyChangeDisabled, WebClientWriteDisabled, WebClientMFADisabled, WebClientOptions = []string{WebClientWriteDisabled, WebClientPasswordChangeDisabled, WebClientPubKeyChangeDisabled,
WebClientPasswordChangeDisabled, WebClientAPIKeyAuthChangeDisabled} WebClientMFADisabled, WebClientAPIKeyAuthChangeDisabled, WebClientInfoChangeDisabled}
// UserTypes defines the supported user type hints for auth plugins // UserTypes defines the supported user type hints for auth plugins
UserTypes = []string{string(UserTypeLDAP), string(UserTypeOS)} UserTypes = []string{string(UserTypeLDAP), string(UserTypeOS)}
) )

View file

@ -115,7 +115,6 @@ func (c *Config) getAuthType() mail.AuthType {
} }
// SendEmail tries to send an email using the specified parameters. // SendEmail tries to send an email using the specified parameters.
// If the contentType is 0 text/plain is assumed, otherwise text/html
func SendEmail(to, subject, body string, contentType EmailContentType) error { func SendEmail(to, subject, body string, contentType EmailContentType) error {
if smtpServer == nil { if smtpServer == nil {
return errors.New("smtp: not configured") return errors.New("smtp: not configured")

View file

@ -22,6 +22,14 @@
</div> </div>
</div> </div>
<div class="form-group row">
<label for="idEmail" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="idEmail" name="email" placeholder=""
value="{{.Admin.Email}}" maxlength="255">
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<label for="idDescription" class="col-sm-2 col-form-label">Description</label> <label for="idDescription" class="col-sm-2 col-form-label">Description</label>
<div class="col-sm-10"> <div class="col-sm-10">
@ -69,14 +77,6 @@
</div> </div>
</div> </div>
<div class="form-group row">
<label for="idEmail" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="idEmail" name="email" placeholder=""
value="{{.Admin.Email}}" maxlength="255">
</div>
</div>
<div class="form-group row"> <div class="form-group row">
<label for="idAllowedIP" class="col-sm-2 col-form-label">Allowed IP/Mask</label> <label for="idAllowedIP" class="col-sm-2 col-form-label">Allowed IP/Mask</label>
<div class="col-sm-10"> <div class="col-sm-10">

View file

@ -167,9 +167,13 @@
</a> </a>
<!-- Dropdown - User Information --> <!-- Dropdown - User Information -->
<div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" aria-labelledby="userDropdown"> <div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" aria-labelledby="userDropdown">
<a class="dropdown-item" href="{{.CredentialsURL}}"> <a class="dropdown-item" href="{{.ProfileURL}}">
<i class="fas fa-user fa-sm fa-fw mr-2 text-gray-400"></i>
Profile
</a>
<a class="dropdown-item" href="{{.ChangePwdURL}}">
<i class="fas fa-key fa-sm fa-fw mr-2 text-gray-400"></i> <i class="fas fa-key fa-sm fa-fw mr-2 text-gray-400"></i>
Credentials Change password
</a> </a>
{{if .LoggedAdmin.CanManageMFA}} {{if .LoggedAdmin.CanManageMFA}}
<a class="dropdown-item" href="{{.MFAURL}}"> <a class="dropdown-item" href="{{.MFAURL}}">

View file

@ -14,7 +14,7 @@
<div class="card-body text-form-error">{{.Error}}</div> <div class="card-body text-form-error">{{.Error}}</div>
</div> </div>
{{end}} {{end}}
<form id="user_form" action="{{.ChangePwdURL}}" method="POST" autocomplete="off"> <form id="user_form" action="{{.CurrentURL}}" method="POST" autocomplete="off">
<div class="form-group row"> <div class="form-group row">
<label for="idCurrentPassword" class="col-sm-2 col-form-label">Current password</label> <label for="idCurrentPassword" class="col-sm-2 col-form-label">Current password</label>
<div class="col-sm-10"> <div class="col-sm-10">
@ -41,31 +41,4 @@
</form> </form>
</div> </div>
</div> </div>
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">REST API access</h6>
</div>
<div class="card-body">
{{if .APIKeyError}}
<div class="card mb-4 border-left-warning">
<div class="card-body text-form-error">{{.APIKeyError}}</div>
</div>
{{end}}
<form id="key_form" action="{{.ManageAPIKeyURL}}" method="POST">
<div class="form-group">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="idAllowAPIKeyAuth" name="allow_api_key_auth"
{{if .AllowAPIKeyAuth}}checked{{end}} aria-describedby="allowAPIKeyAuthHelpBlock">
<label for="idAllowAPIKeyAuth" class="form-check-label">Allow API key authentication</label>
<small id="allowAPIKeyAuthHelpBlock" class="form-text text-muted">
Allow to impersonate yourself, in REST API, with an API key. If this permission is not granted, your credentials are required to use the REST API on your behalf
</small>
</div>
</div>
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Submit</button>
</form>
</div>
</div>
{{end}} {{end}}

View file

@ -0,0 +1,50 @@
{{template "base" .}}
{{define "title"}}{{.Title}}{{end}}
{{define "page_body"}}
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">My profile - {{.LoggedAdmin.Username}}</h6>
</div>
<div class="card-body">
{{if .Error}}
<div class="card mb-4 border-left-warning">
<div class="card-body text-form-error">{{.Error}}</div>
</div>
{{end}}
<form id="profile_form" action="{{.CurrentURL}}" method="POST">
<div class="form-group row">
<label for="idEmail" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="idEmail" name="email" placeholder=""
value="{{.Email}}" maxlength="255">
</div>
</div>
<div class="form-group row">
<label for="idDescription" class="col-sm-2 col-form-label">Description</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="idDescription" name="description" placeholder=""
value="{{.Description}}" maxlength="255">
</div>
</div>
<div class="form-group">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="idAllowAPIKeyAuth" name="allow_api_key_auth"
{{if .AllowAPIKeyAuth}}checked{{end}} aria-describedby="allowAPIKeyAuthHelpBlock">
<label for="idAllowAPIKeyAuth" class="form-check-label">Allow API key authentication</label>
<small id="allowAPIKeyAuthHelpBlock" class="form-text text-muted">
Allow to impersonate yourself, in REST API, with an API key. If this permission is not granted, your credentials are required to use the REST API on your behalf
</small>
</div>
</div>
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Submit</button>
</form>
</div>
</div>
{{end}}

View file

@ -79,10 +79,10 @@
</a> </a>
</li> </li>
<li class="nav-item {{if eq .CurrentURL .CredentialsURL}}active{{end}}"> <li class="nav-item {{if eq .CurrentURL .ProfileURL}}active{{end}}">
<a class="nav-link" href="{{.CredentialsURL}}"> <a class="nav-link" href="{{.ProfileURL}}">
<i class="fas fa-key"></i> <i class="fas fa-user"></i>
<span>{{.CredentialsTitle}}</span></a> <span>{{.ProfileTitle}}</span></a>
</li> </li>
{{if .LoggedUser.CanManageMFA}} {{if .LoggedUser.CanManageMFA}}
<li class="nav-item {{if eq .CurrentURL .MFAURL}}active{{end}}"> <li class="nav-item {{if eq .CurrentURL .MFAURL}}active{{end}}">
@ -129,6 +129,13 @@
</a> </a>
<!-- Dropdown - User Information --> <!-- Dropdown - User Information -->
<div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" aria-labelledby="userDropdown"> <div class="dropdown-menu dropdown-menu-right shadow animated--grow-in" aria-labelledby="userDropdown">
{{if .LoggedUser.CanChangePassword}}
<a class="dropdown-item" href="{{.ChangePwdURL}}">
<i class="fas fa-key fa-sm fa-fw mr-2 text-gray-400"></i>
Change password
</a>
<div class="dropdown-divider"></div>
{{end}}
<a class="dropdown-item" href="#" data-toggle="modal" data-target="#logoutModal"> <a class="dropdown-item" href="#" data-toggle="modal" data-target="#logoutModal">
<i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i> <i class="fas fa-sign-out-alt fa-sm fa-fw mr-2 text-gray-400"></i>
Logout Logout

View file

@ -0,0 +1,44 @@
{{template "base" .}}
{{define "title"}}{{.Title}}{{end}}
{{define "page_body"}}
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Change password</h6>
</div>
<div class="card-body">
{{if .Error}}
<div class="card mb-4 border-left-warning">
<div class="card-body text-form-error">{{.Error}}</div>
</div>
{{end}}
<form id="user_form" action="{{.CurrentURL}}" method="POST" autocomplete="off">
<div class="form-group row">
<label for="idCurrentPassword" class="col-sm-2 col-form-label">Current password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="idCurrentPassword" name="current_password" required>
</div>
</div>
<div class="form-group row">
<label for="idNewPassword1" class="col-sm-2 col-form-label">New password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="idNewPassword1" name="new_password1" required>
</div>
</div>
<div class="form-group row">
<label for="idNewPassword2" class="col-sm-2 col-form-label">Confirm password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="idNewPassword2" name="new_password2" required>
</div>
</div>
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Change my password</button>
</form>
</div>
</div>
{{end}}

View file

@ -1,158 +0,0 @@
{{template "base" .}}
{{define "title"}}{{.Title}}{{end}}
{{define "page_body"}}
{{if .LoggedUser.CanChangePassword}}
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Change password</h6>
</div>
<div class="card-body">
{{if .PwdError}}
<div class="card mb-4 border-left-warning">
<div class="card-body text-form-error">{{.PwdError}}</div>
</div>
{{end}}
<form id="user_form" action="{{.ChangePwdURL}}" method="POST" autocomplete="off">
<div class="form-group row">
<label for="idCurrentPassword" class="col-sm-2 col-form-label">Current password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="idCurrentPassword" name="current_password" required>
</div>
</div>
<div class="form-group row">
<label for="idNewPassword1" class="col-sm-2 col-form-label">New password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="idNewPassword1" name="new_password1" required>
</div>
</div>
<div class="form-group row">
<label for="idNewPassword2" class="col-sm-2 col-form-label">Confirm password</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="idNewPassword2" name="new_password2" required>
</div>
</div>
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Change my password</button>
</form>
</div>
</div>
{{end}}
{{if .LoggedUser.CanManagePublicKeys}}
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">Manage public keys</h6>
</div>
<div class="card-body">
{{if .KeyError}}
<div class="card mb-4 border-left-warning">
<div class="card-body text-form-error">{{.KeyError}}</div>
</div>
{{end}}
<form id="key_form" action="{{.ManageKeysURL}}" method="POST">
<div class="form-group row">
<div class="col-md-12 form_field_pk_outer">
{{range $idx, $val := .PublicKeys}}
<div class="row form_field_pk_outer_row">
<div class="form-group col-md-11">
<textarea class="form-control" id="idPublicKey{{$idx}}" name="public_keys" rows="4"
placeholder="Paste your public key here">{{$val}}</textarea>
</div>
<div class="form-group col-md-1">
<button class="btn btn-circle btn-danger remove_pk_btn_frm_field">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
{{else}}
<div class="row form_field_pk_outer_row">
<div class="form-group col-md-11">
<textarea class="form-control" id="idPublicKey0" name="public_keys" rows="4"
placeholder="Paste your public key here"></textarea>
</div>
<div class="form-group col-md-1">
<button class="btn btn-circle btn-danger remove_pk_btn_frm_field" disabled>
<i class="fas fa-trash"></i>
</button>
</div>
</div>
{{end}}
</div>
</div>
<div class="row mx-1">
<button type="button" class="btn btn-secondary add_new_pk_field_btn">
<i class="fas fa-plus"></i> Add new public key
</button>
</div>
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Submit</button>
</form>
</div>
</div>
{{end}}
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">REST API access</h6>
</div>
<div class="card-body">
{{if .APIKeyError}}
<div class="card mb-4 border-left-warning">
<div class="card-body text-form-error">{{.APIKeyError}}</div>
</div>
{{end}}
<form id="key_form" action="{{.ManageAPIKeyURL}}" method="POST">
<div class="form-group">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="idAllowAPIKeyAuth" name="allow_api_key_auth" {{if not .LoggedUser.CanChangeAPIKeyAuth}}disabled="disabled"{{end}}
{{if .AllowAPIKeyAuth}}checked{{end}} aria-describedby="allowAPIKeyAuthHelpBlock">
<label for="idAllowAPIKeyAuth" class="form-check-label">Allow API key authentication</label>
<small id="allowAPIKeyAuthHelpBlock" class="form-text text-muted">
Allow to impersonate yourself, in REST API, with an API key. If this permission is not granted, your credentials are required to use the REST API on your behalf
</small>
</div>
</div>
{{if .LoggedUser.CanChangeAPIKeyAuth}}
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Submit</button>
{{end}}
</form>
</div>
</div>
{{end}}
{{define "extra_js"}}
<script type="text/javascript">
$(document).ready(function () {
$("body").on("click", ".add_new_pk_field_btn", function () {
var index = $(".form_field_pk_outer").find(".form_field_pk_outer_row").length;
while (document.getElementById("idPublicKey"+index) != null){
index++;
}
$(".form_field_pk_outer").append(`
<div class="row form_field_pk_outer_row">
<div class="form-group col-md-11">
<textarea class="form-control" id="idPublicKey${index}" name="public_keys" rows="4"
placeholder="Paste your public key here"></textarea>
</div>
<div class="form-group col-md-1">
<button class="btn btn-circle btn-danger remove_pk_btn_frm_field">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
`);
});
$("body").on("click", ".remove_pk_btn_frm_field", function () {
$(this).closest(".form_field_pk_outer_row").remove();
});
});
</script>
{{end}}

View file

@ -0,0 +1,128 @@
{{template "base" .}}
{{define "title"}}{{.Title}}{{end}}
{{define "page_body"}}
<div class="card shadow mb-4">
<div class="card-header py-3">
<h6 class="m-0 font-weight-bold text-primary">My profile - {{.LoggedUser.Username}}</h6>
</div>
<div class="card-body">
{{if .Error}}
<div class="card mb-4 border-left-warning">
<div class="card-body text-form-error">{{.Error}}</div>
</div>
{{end}}
<form id="profile_form" action="{{.CurrentURL}}" method="POST">
<div class="form-group row">
<label for="idEmail" class="col-sm-2 col-form-label">Email</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="idEmail" name="email" placeholder=""
value="{{.Email}}" maxlength="255" autocomplete="nope" {{if not .LoggedUser.CanChangeInfo}}readonly{{end}}>
</div>
</div>
<div class="form-group row">
<label for="idDescription" class="col-sm-2 col-form-label">Description</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="idDescription" name="description" placeholder=""
value="{{.Description}}" maxlength="255" {{if not .LoggedUser.CanChangeInfo}}readonly{{end}}>
</div>
</div>
<div class="form-group">
<div class="form-check">
<input type="checkbox" class="form-check-input" id="idAllowAPIKeyAuth" name="allow_api_key_auth" {{if not .LoggedUser.CanChangeAPIKeyAuth}}disabled="disabled"{{end}}
{{if .AllowAPIKeyAuth}}checked{{end}} aria-describedby="allowAPIKeyAuthHelpBlock">
<label for="idAllowAPIKeyAuth" class="form-check-label">Allow API key authentication</label>
<small id="allowAPIKeyAuthHelpBlock" class="form-text text-muted">
Allow to impersonate yourself, in REST API, with an API key. If this permission is not granted, your credentials are required to use the REST API on your behalf
</small>
</div>
</div>
{{if .LoggedUser.CanManagePublicKeys}}
<div class="card bg-light mb-3">
<div class="card-header">
Public keys
</div>
<div class="card-body">
<div class="form-group row">
<div class="col-md-12 form_field_pk_outer">
{{range $idx, $val := .PublicKeys}}
<div class="row form_field_pk_outer_row">
<div class="form-group col-md-11">
<textarea class="form-control" id="idPublicKey{{$idx}}" name="public_keys" rows="4"
placeholder="Paste your public key here">{{$val}}</textarea>
</div>
<div class="form-group col-md-1">
<button class="btn btn-circle btn-danger remove_pk_btn_frm_field">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
{{else}}
<div class="row form_field_pk_outer_row">
<div class="form-group col-md-11">
<textarea class="form-control" id="idPublicKey0" name="public_keys" rows="4"
placeholder="Paste your public key here"></textarea>
</div>
<div class="form-group col-md-1">
<button class="btn btn-circle btn-danger remove_pk_btn_frm_field" disabled>
<i class="fas fa-trash"></i>
</button>
</div>
</div>
{{end}}
</div>
</div>
<div class="row mx-1">
<button type="button" class="btn btn-secondary add_new_pk_field_btn">
<i class="fas fa-plus"></i> Add new public key
</button>
</div>
</div>
</div>
{{end}}
{{if .CanSubmit}}
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
<button type="submit" class="btn btn-primary float-right mt-3 px-5 px-3">Submit</button>
{{end}}
</form>
</div>
</div>
{{end}}
{{define "extra_js"}}
{{if .LoggedUser.CanManagePublicKeys}}
<script type="text/javascript">
$(document).ready(function () {
$("body").on("click", ".add_new_pk_field_btn", function () {
var index = $(".form_field_pk_outer").find(".form_field_pk_outer_row").length;
while (document.getElementById("idPublicKey"+index) != null){
index++;
}
$(".form_field_pk_outer").append(`
<div class="row form_field_pk_outer_row">
<div class="form-group col-md-11">
<textarea class="form-control" id="idPublicKey${index}" name="public_keys" rows="4"
placeholder="Paste your public key here"></textarea>
</div>
<div class="form-group col-md-1">
<button class="btn btn-circle btn-danger remove_pk_btn_frm_field">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
`);
});
$("body").on("click", ".remove_pk_btn_frm_field", function () {
$(this).closest(".form_field_pk_outer_row").remove();
});
});
</script>
{{end}}
{{end}}