rework user and admin profiles
users and admins can now also update their email and description
This commit is contained in:
parent
af8fa7ff81
commit
ba1febba73
25 changed files with 1038 additions and 798 deletions
14
.github/workflows/development.yml
vendored
14
.github/workflows/development.yml
vendored
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
16
go.mod
|
@ -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
34
go.sum
|
@ -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=
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
311
httpd/httpd.go
311
httpd/httpd.go
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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)}
|
||||||
)
|
)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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">
|
||||||
|
|
|
@ -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}}">
|
||||||
|
|
|
@ -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}}
|
50
templates/webadmin/profile.html
Normal file
50
templates/webadmin/profile.html
Normal 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}}
|
|
@ -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
|
||||||
|
|
44
templates/webclient/changepassword.html
Normal file
44
templates/webclient/changepassword.html
Normal 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}}
|
|
@ -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}}
|
|
128
templates/webclient/profile.html
Normal file
128
templates/webclient/profile.html
Normal 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}}
|
Loading…
Reference in a new issue