From fdcda4e507521e374edc96e2ca4a266b9b767eb3 Mon Sep 17 00:00:00 2001 From: zachmann Date: Fri, 9 Apr 2021 12:23:45 +0200 Subject: [PATCH 01/61] change hmac key message --- internal/db/dbrepo/authcodeinforepo/state/consentcode.go | 2 +- internal/db/dbrepo/authcodeinforepo/state/state.go | 2 +- internal/model/version/version.go | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/db/dbrepo/authcodeinforepo/state/consentcode.go b/internal/db/dbrepo/authcodeinforepo/state/consentcode.go index 79020d48..95ba2320 100644 --- a/internal/db/dbrepo/authcodeinforepo/state/consentcode.go +++ b/internal/db/dbrepo/authcodeinforepo/state/consentcode.go @@ -39,7 +39,7 @@ func (c *ConsentCode) String() string { func (c *ConsentCode) GetState() string { if c.state == "" { - c.state = hashUtils.HMACSHA512Str([]byte(c.r), []byte("state"))[:stateLen] + c.encodedInfo + c.state = hashUtils.HMACSHA512Str([]byte("state"), []byte(c.r))[:stateLen] + c.encodedInfo } return c.state } diff --git a/internal/db/dbrepo/authcodeinforepo/state/state.go b/internal/db/dbrepo/authcodeinforepo/state/state.go index 32829525..3937493e 100644 --- a/internal/db/dbrepo/authcodeinforepo/state/state.go +++ b/internal/db/dbrepo/authcodeinforepo/state/state.go @@ -32,7 +32,7 @@ func (s *State) Hash() string { func (s *State) PollingCode() string { if s.pollingCode == "" { - s.pollingCode = hashUtils.HMACSHA512Str([]byte(s.state), []byte("polling_code"))[:config.Get().Features.Polling.Len] + s.pollingCode = hashUtils.HMACSHA512Str([]byte("polling_code"), []byte(s.state))[:config.Get().Features.Polling.Len] log.WithField("state", s.state).WithField("polling_code", s.pollingCode).Debug("Created polling_code for state") } return s.pollingCode diff --git a/internal/model/version/version.go b/internal/model/version/version.go index 8533c602..1c9fa822 100644 --- a/internal/model/version/version.go +++ b/internal/model/version/version.go @@ -8,8 +8,8 @@ import ( const ( MAJOR = 0 MINOR = 2 - FIX = 0 - DEV = false + FIX = 1 + DEV = true ) var version = fmt.Sprintf("%d.%d.%d", MAJOR, MINOR, FIX) From fe57eae8996dd8528d9289b59fa6dbeb8d07aed0 Mon Sep 17 00:00:00 2001 From: zachmann Date: Tue, 4 May 2021 15:43:19 +0200 Subject: [PATCH 02/61] add version to mytoken --- pkg/api/v0/mytoken.go | 36 +++++++++++++++++++++++++++++++++++ shared/mytoken/pkg/mytoken.go | 2 ++ 2 files changed, 38 insertions(+) diff --git a/pkg/api/v0/mytoken.go b/pkg/api/v0/mytoken.go index 0448c514..81f123de 100644 --- a/pkg/api/v0/mytoken.go +++ b/pkg/api/v0/mytoken.go @@ -1,7 +1,13 @@ package api +import ( + "encoding/json" + "fmt" +) + // Mytoken is a mytoken Mytoken type Mytoken struct { + Version TokenVersion `json:"ver"` Issuer string `json:"iss"` Subject string `json:"sub"` ExpiresAt int64 `json:"exp,omitempty"` @@ -18,6 +24,11 @@ type Mytoken struct { Rotation Rotation `json:"rotation,omitempty"` } +var TokenVer = TokenVersion{ + Major: 0, + Minor: 1, +} + // UsedMytoken is a type for a Mytoken that has been used, it additionally has information how often it has been used type UsedMytoken struct { Mytoken `json:",inline"` @@ -29,3 +40,28 @@ type Rotation struct { OnOther bool `json:"on_other,omitempty"` Lifetime uint64 `json:"lifetime,omitempty"` } + +type TokenVersion struct { + Major int + Minor int +} + +const vFmt = "%d.%d" + +func (v TokenVersion) String() string { + return fmt.Sprintf(vFmt, v.Major, v.Minor) +} +func (v TokenVersion) Version() string { + return v.String() +} +func (v TokenVersion) MarshalJSON() ([]byte, error) { + return json.Marshal(v.String()) +} +func (v *TokenVersion) UnmarshalJSON(data []byte) error { + var str string + if err := json.Unmarshal(data, &str); err != nil { + return err + } + _, err := fmt.Sscanf(str, vFmt, &v.Major, &v.Minor) + return err +} diff --git a/shared/mytoken/pkg/mytoken.go b/shared/mytoken/pkg/mytoken.go index 73cb39b9..831e0320 100644 --- a/shared/mytoken/pkg/mytoken.go +++ b/shared/mytoken/pkg/mytoken.go @@ -25,6 +25,7 @@ import ( // Mytoken is a mytoken Mytoken type Mytoken struct { // On update also update api.Mytoken + Version api.TokenVersion `json:"ver"` Issuer string `json:"iss"` Subject string `json:"sub"` ExpiresAt unixtime.UnixTime `json:"exp,omitempty"` @@ -73,6 +74,7 @@ func (mt *Mytoken) VerifyCapabilities(required ...api.Capability) bool { func NewMytoken(oidcSub, oidcIss string, r restrictions.Restrictions, c, sc api.Capabilities) *Mytoken { now := unixtime.Now() mt := &Mytoken{ + Version: api.TokenVer, ID: mtid.New(), SeqNo: 1, IssuedAt: now, From d4781d25028d1c9b84c8c321db49152bba607655 Mon Sep 17 00:00:00 2001 From: zachmann Date: Thu, 6 May 2021 17:59:05 +0200 Subject: [PATCH 03/61] add option to set maximum lifetime of mytokens --- config/example-config.yaml | 5 ++++- internal/config/config.go | 1 + shared/mytoken/mytokenHandler.go | 8 +++++++- shared/mytoken/pkg/mytoken.go | 1 + shared/mytoken/restrictions/restriction.go | 23 ++++++++++++++++++++++ 5 files changed, 36 insertions(+), 2 deletions(-) diff --git a/config/example-config.yaml b/config/example-config.yaml index 1de1ce07..d1122b78 100644 --- a/config/example-config.yaml +++ b/config/example-config.yaml @@ -78,7 +78,6 @@ features: # The supported oidc flows oidc_flows: - "authorization_code" # Always enabled - - "device" # Requires polling_codes to be enabled # Revocation for tokens issued by mytoken. Only disable this if you have good reasons for it. token_revocation: @@ -137,3 +136,7 @@ providers: scopes: - openid - profile + # Maximum lifetime for mytokens for this issuer, given in seconds. On default the lifetime of mytokens is not restricted. + # Setting this value to 0, means that there is no maximum lifetime. + mytokens_max_lifetime: 0 + diff --git a/internal/config/config.go b/internal/config/config.go index 4d110732..cc8a38b4 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -179,6 +179,7 @@ type ProviderConf struct { ClientID string `yaml:"client_id"` ClientSecret string `yaml:"client_secret"` Scopes []string `yaml:"scopes"` + MytokensMaxLifetime int64 `yaml:"mytokens_max_lifetime"` Endpoints *oauth2x.Endpoints `yaml:"-"` Provider *oidc.Provider `yaml:"-"` Name string `yaml:"name"` diff --git a/shared/mytoken/mytokenHandler.go b/shared/mytoken/mytokenHandler.go index b680267a..b3ed5fa4 100644 --- a/shared/mytoken/mytokenHandler.go +++ b/shared/mytoken/mytokenHandler.go @@ -229,11 +229,17 @@ func createMytokenEntry(parent *mytoken.Mytoken, req *response.MytokenFromMytoke if !rootID.HashValid() { rootID = parent.ID } + if changed := req.Restrictions.EnforceMaxLifetime(parent.OIDCIssuer); changed && req.FailOnRestrictionsNotTighter { + return nil, &model.Response{ + Status: fiber.StatusBadRequest, + Response: pkgModel.BadRequestError("requested restrictions do not respect maximum mytoken lifetime"), + } + } r, ok := restrictions.Tighten(parent.Restrictions, req.Restrictions) if !ok && req.FailOnRestrictionsNotTighter { return nil, &model.Response{ Status: fiber.StatusBadRequest, - Response: pkgModel.BadRequestError("requested restrictions are not superset of original restrictions"), + Response: pkgModel.BadRequestError("requested restrictions are not subset of original restrictions"), } } capsFromParent := parent.SubtokenCapabilities diff --git a/shared/mytoken/pkg/mytoken.go b/shared/mytoken/pkg/mytoken.go index 831e0320..fd1de442 100644 --- a/shared/mytoken/pkg/mytoken.go +++ b/shared/mytoken/pkg/mytoken.go @@ -87,6 +87,7 @@ func NewMytoken(oidcSub, oidcIss string, r restrictions.Restrictions, c, sc api. Capabilities: c, SubtokenCapabilities: sc, } + r.EnforceMaxLifetime(oidcIss) if len(r) > 0 { mt.Restrictions = r exp := r.GetExpires() diff --git a/shared/mytoken/restrictions/restriction.go b/shared/mytoken/restrictions/restriction.go index 2b9ee6a6..a2887e16 100644 --- a/shared/mytoken/restrictions/restriction.go +++ b/shared/mytoken/restrictions/restriction.go @@ -8,6 +8,7 @@ import ( "github.com/jinzhu/copier" "github.com/jmoiron/sqlx" + "github.com/oidc-mytoken/server/internal/config" log "github.com/sirupsen/logrus" "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/mytokenrepohelper" @@ -327,6 +328,28 @@ func (r *Restrictions) SetMaxAudiences(mAud []string) { } } +// EnforceMaxLifetime enforces the maximum mytoken lifetime set by server admins. Returns true if the restrictions was changed. +func (r *Restrictions) EnforceMaxLifetime(issuer string) (changed bool) { + maxLifetime := config.Get().ProviderByIssuer[issuer].MytokensMaxLifetime + if maxLifetime == 0 { + return + } + exp := unixtime.InSeconds(maxLifetime) + if len(*r) == 0 { + *r = append(*r, Restriction{ExpiresAt: exp}) + changed = true + return + } + for i, rr := range *r { + if rr.ExpiresAt == 0 || rr.ExpiresAt > exp { + rr.ExpiresAt = exp + (*r)[i] = rr + changed = true + } + } + return +} + // Tighten tightens/restricts a Restrictions with another set; if the wanted Restrictions are not tighter the original ones are returned func Tighten(old, wanted Restrictions) (res Restrictions, ok bool) { if len(old) == 0 { From 02cf977ecf8f984eec12dd749ac7aae7550715ec Mon Sep 17 00:00:00 2001 From: zachmann Date: Thu, 6 May 2021 18:02:19 +0200 Subject: [PATCH 04/61] fix colorization of restriction icons on consent screen if mytoken is unrestricted --- internal/server/web/static/js/consent.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/server/web/static/js/consent.js b/internal/server/web/static/js/consent.js index 360f501b..6612ce4b 100644 --- a/internal/server/web/static/js/consent.js +++ b/internal/server/web/static/js/consent.js @@ -40,7 +40,7 @@ function parseRestriction() { let iconScope = $('#r-icon-scope'); let iconAud = $('#r-icon-aud'); let iconUsages = $('#r-icon-usages'); - if (howManyClausesRestrictIP===restrictions.length) { + if (howManyClausesRestrictIP===restrictions.length && restrictions.length > 0) { iconIP.addClass( 'text-success'); iconIP.removeClass( 'text-warning'); iconIP.removeClass( 'text-danger'); @@ -51,7 +51,7 @@ function parseRestriction() { iconIP.removeClass( 'text-danger'); iconIP.attr('data-original-title', "This token can be used from any IP."); } - if (howManyClausesRestrictScope===restrictions.length) { + if (howManyClausesRestrictScope===restrictions.length && restrictions.length > 0) { iconScope.addClass( 'text-success'); iconScope.removeClass( 'text-warning'); iconScope.removeClass( 'text-danger'); @@ -62,7 +62,7 @@ function parseRestriction() { iconScope.removeClass( 'text-danger'); iconScope.attr('data-original-title', "This token can use all configured scopes."); } - if (howManyClausesRestrictAud===restrictions.length) { + if (howManyClausesRestrictAud===restrictions.length && restrictions.length > 0) { iconAud.addClass( 'text-success'); iconAud.removeClass( 'text-warning'); iconAud.removeClass( 'text-danger'); @@ -73,7 +73,7 @@ function parseRestriction() { iconAud.removeClass( 'text-danger'); iconAud.attr('data-original-title', "This token can obtain access tokens with any audiences."); } - if (howManyClausesRestrictUsages===restrictions.length) { + if (howManyClausesRestrictUsages===restrictions.length && restrictions.length > 0) { iconUsages.addClass( 'text-success'); iconUsages.removeClass( 'text-warning'); iconUsages.removeClass( 'text-danger'); From 5f00e7c304aeaa6d4522b882e94cc16be9921ef5 Mon Sep 17 00:00:00 2001 From: zachmann Date: Thu, 20 May 2021 08:25:20 +0200 Subject: [PATCH 05/61] set cookie secure if issuer uses https indepent of a potentail proxy --- internal/config/config.go | 9 +++++++++ internal/endpoints/revocation/revocationEndpoint.go | 2 +- internal/oidc/authcode/authcode.go | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index cc8a38b4..8e54cac5 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -2,6 +2,7 @@ package config import ( "fmt" + "strings" "github.com/coreos/go-oidc/v3/oidc" log "github.com/sirupsen/logrus" @@ -22,6 +23,7 @@ var defaultConfig = Config{ Enabled: true, // The default is that TLS is enabled if cert and key are given, this is checked later; we must set true here, because otherwise we cannot distinct this from a false set by the user RedirectHTTP: true, }, + Secure: true, }, DB: DBConf{ Hosts: []string{"localhost"}, @@ -158,6 +160,7 @@ type serverConf struct { Hostname string `yaml:"hostname"` Port int `yaml:"port"` TLS tlsConf `yaml:"tls"` + Secure bool `yaml:"-"` // Secure indicates if the connection to the mytoken server is secure. This is independent of TLS, e.g. a Proxy can be used. } type tlsConf struct { @@ -217,6 +220,12 @@ func validate() error { if conf == nil { return fmt.Errorf("config not set") } + if conf.IssuerURL == "" { + return fmt.Errorf("invalid config:issuer_url not set") + } + if strings.HasPrefix(conf.IssuerURL, "http://") { + conf.Server.Secure = false + } if conf.Server.Hostname == "" { return fmt.Errorf("invalid config: server.hostname not set") } diff --git a/internal/endpoints/revocation/revocationEndpoint.go b/internal/endpoints/revocation/revocationEndpoint.go index 036eb2f5..332f2e97 100644 --- a/internal/endpoints/revocation/revocationEndpoint.go +++ b/internal/endpoints/revocation/revocationEndpoint.go @@ -46,7 +46,7 @@ func HandleRevoke(ctx *fiber.Ctx) error { Value: "", Path: "/api", Expires: time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC), - Secure: config.Get().Server.TLS.Enabled, + Secure: config.Get().Server.Secure, HTTPOnly: true, SameSite: "Strict", }}, diff --git a/internal/oidc/authcode/authcode.go b/internal/oidc/authcode/authcode.go index 2a6b8439..88b195c4 100644 --- a/internal/oidc/authcode/authcode.go +++ b/internal/oidc/authcode/authcode.go @@ -244,7 +244,7 @@ func CodeExchange(oState *state.State, code string, networkData api.ClientMetaDa Value: cookieValue, Path: "/api", MaxAge: cookieAge, - Secure: config.Get().Server.TLS.Enabled, + Secure: config.Get().Server.Secure, HTTPOnly: true, SameSite: "Strict", }}, From 07298fd1c697c782ca6983c369963dc9bfe1b351 Mon Sep 17 00:00:00 2001 From: zachmann Date: Wed, 26 May 2021 08:19:00 +0200 Subject: [PATCH 06/61] change bin dir in packages --- .goreleaser.yml | 3 +++ config/mytoken.service | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.goreleaser.yml b/.goreleaser.yml index db26df95..699d042e 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -48,6 +48,7 @@ nfpms: - rpm release: 1 section: misc + bindir: /usr/bin empty_folders: - /var/log/mytoken contents: @@ -69,6 +70,7 @@ nfpms: - rpm release: 1 section: misc + bindir: /usr/bin - id: garbage-pkg package_name: mytoken-server-dbgc builds: @@ -82,6 +84,7 @@ nfpms: - rpm release: 1 section: misc + bindir: /usr/bin checksum: name_template: 'checksums.txt' snapshot: diff --git a/config/mytoken.service b/config/mytoken.service index c0f42b77..9737223b 100644 --- a/config/mytoken.service +++ b/config/mytoken.service @@ -3,7 +3,7 @@ After=network.target auditd.service [Service] - ExecStart=/usr/local/bin/mytoken-server + ExecStart=/usr/bin/mytoken-server ExecReload=/bin/kill -HUP $MAINPID WorkingDirectory=/ StandardOutput=inherit From a1fdef4ff014071a2b66c39e88cc38e00464d10d Mon Sep 17 00:00:00 2001 From: zachmann Date: Tue, 1 Jun 2021 13:23:27 +0200 Subject: [PATCH 07/61] add docker files --- .gitignore | 1 + .goreleaser.yml | 52 +++++++++++++++++++ cmd/mytoken-server/Dockerfile | 9 ++++ .../mytoken-dbGarbageCollector/Dockerfile | 9 ++++ cmd/mytoken-server/mytoken-setup/Dockerfile | 9 ++++ 5 files changed, 80 insertions(+) create mode 100644 cmd/mytoken-server/Dockerfile create mode 100644 cmd/mytoken-server/mytoken-dbGarbageCollector/Dockerfile create mode 100644 cmd/mytoken-server/mytoken-setup/Dockerfile diff --git a/.gitignore b/.gitignore index 56aac10a..98eb4b90 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ tags client.config config/config.yaml +config/docker-config.yaml IP2LOCATION-LITE-DB1.IPV6.BIN /cmd/test generateDDL.sh diff --git a/.goreleaser.yml b/.goreleaser.yml index 699d042e..8ec49a71 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -85,6 +85,58 @@ nfpms: release: 1 section: misc bindir: /usr/bin +dockers: + - goos: linux + goarch: amd64 + ids: + - server + image_templates: + - "oidcmytoken/mytoken-server:latest" + - "oidcmytoken/mytoken-server:{{ .Tag }}" + - "oidcmytoken/mytoken-server:v{{ .Major }}" + - "oidcmytoken/mytoken-server:v{{ .Major }}.{{ .Minor }}" + skip_push: true + dockerfile: cmd/mytoken-server/Dockerfile + build_flag_templates: + - "--pull" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title=mytoken-server" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - goos: linux + goarch: amd64 + ids: + - setup + image_templates: + - "oidcmytoken/mytoken-setup:latest" + - "oidcmytoken/mytoken-setup:{{ .Tag }}" + - "oidcmytoken/mytoken-setup:v{{ .Major }}" + - "oidcmytoken/mytoken-setup:v{{ .Major }}.{{ .Minor }}" + skip_push: true + dockerfile: cmd/mytoken-server/mytoken-setup/Dockerfile + build_flag_templates: + - "--pull" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title=mytoken-setup" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - goos: linux + goarch: amd64 + ids: + - garbage + image_templates: + - "oidcmytoken/mytoken-dbgc:latest" + - "oidcmytoken/mytoken-dbgc:{{ .Tag }}" + - "oidcmytoken/mytoken-dbgc:v{{ .Major }}" + - "oidcmytoken/mytoken-dbgc:v{{ .Major }}.{{ .Minor }}" + skip_push: true + dockerfile: cmd/mytoken-server/mytoken-dbGarbageCollector/Dockerfile + build_flag_templates: + - "--pull" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title=mytoken-dbgc" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" checksum: name_template: 'checksums.txt' snapshot: diff --git a/cmd/mytoken-server/Dockerfile b/cmd/mytoken-server/Dockerfile new file mode 100644 index 00000000..a5b5a184 --- /dev/null +++ b/cmd/mytoken-server/Dockerfile @@ -0,0 +1,9 @@ +FROM debian:stable +RUN apt-get update && \ + apt-get install ca-certificates -y && \ + apt-get autoremove -y && \ + apt-get clean -y && \ + rm -rf /var/lib/apt/lists/* +WORKDIR /mytoken +COPY mytoken-server /usr/bin/mytoken-server +CMD ["mytoken-server"] diff --git a/cmd/mytoken-server/mytoken-dbGarbageCollector/Dockerfile b/cmd/mytoken-server/mytoken-dbGarbageCollector/Dockerfile new file mode 100644 index 00000000..7104472f --- /dev/null +++ b/cmd/mytoken-server/mytoken-dbGarbageCollector/Dockerfile @@ -0,0 +1,9 @@ +FROM debian:stable +RUN apt-get update && \ + apt-get install ca-certificates -y && \ + apt-get autoremove -y && \ + apt-get clean -y && \ + rm -rf /var/lib/apt/lists/* +WORKDIR /mytoken +COPY mytoken-dbgc /usr/bin/mytoken-dbgc +CMD ["mytoken-dbgc"] diff --git a/cmd/mytoken-server/mytoken-setup/Dockerfile b/cmd/mytoken-server/mytoken-setup/Dockerfile new file mode 100644 index 00000000..0ac566f4 --- /dev/null +++ b/cmd/mytoken-server/mytoken-setup/Dockerfile @@ -0,0 +1,9 @@ +FROM debian:stable +RUN apt-get update && \ + apt-get install ca-certificates -y && \ + apt-get autoremove -y && \ + apt-get clean -y && \ + rm -rf /var/lib/apt/lists/* +WORKDIR /mytoken +COPY mytoken-setup /usr/bin/mytoken-setup +ENTRYPOINT ["mytoken-setup"] From a13e9c8fbc882e5d23b2e5ea57d1c9461b5e2d3f Mon Sep 17 00:00:00 2001 From: zachmann Date: Tue, 1 Jun 2021 13:23:49 +0200 Subject: [PATCH 08/61] replace repaeted hard-coded error string --- shared/utils/cryptUtils/cryptUtils.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/shared/utils/cryptUtils/cryptUtils.go b/shared/utils/cryptUtils/cryptUtils.go index f63b5ff8..604e2984 100644 --- a/shared/utils/cryptUtils/cryptUtils.go +++ b/shared/utils/cryptUtils/cryptUtils.go @@ -20,6 +20,9 @@ const ( keyIterations = 8 ) +const malFormedCiphertext = "malformed ciphertext" +const malFormedCiphertextFmt = malFormedCiphertext + ": %s" + func RandomBytes(size int) []byte { r := make([]byte, size) if _, err := rand.Read(r); err != nil { @@ -76,7 +79,7 @@ func aesDecrypt(cipher, password string, keyLen int) (string, error) { arr := strings.SplitN(cipher, "-", 2) salt, err := base64.StdEncoding.DecodeString(arr[0]) if err != nil { - return "", fmt.Errorf("malformed ciphertext: %s", err) + return "", fmt.Errorf(malFormedCiphertextFmt, err) } key, _ := deriveKey(password, salt, keyLen) return AESDecrypt(arr[1], key) @@ -85,15 +88,15 @@ func aesDecrypt(cipher, password string, keyLen int) (string, error) { func AESDecrypt(cipher string, key []byte) (string, error) { arr := strings.Split(cipher, "-") if len(arr) != 2 { - return "", fmt.Errorf("malformed ciphertext") + return "", fmt.Errorf(malFormedCiphertext) } nonce, err := base64.StdEncoding.DecodeString(arr[0]) if err != nil { - return "", fmt.Errorf("malformed ciphertext: %s", err) + return "", fmt.Errorf(malFormedCiphertextFmt, err) } data, err := base64.StdEncoding.DecodeString(arr[1]) if err != nil { - return "", fmt.Errorf("malformed ciphertext: %s", err) + return "", fmt.Errorf(malFormedCiphertextFmt, err) } return aesD(data, nonce, key) } From b3c7a9eb8f9d65caef2d0ec5d7eb1eb08375e97d Mon Sep 17 00:00:00 2001 From: zachmann Date: Tue, 1 Jun 2021 13:24:13 +0200 Subject: [PATCH 09/61] add ternary util --- shared/utils/ternary/ternary.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 shared/utils/ternary/ternary.go diff --git a/shared/utils/ternary/ternary.go b/shared/utils/ternary/ternary.go new file mode 100644 index 00000000..67c8b098 --- /dev/null +++ b/shared/utils/ternary/ternary.go @@ -0,0 +1,17 @@ +package ternary + +// IfNotEmptyOr returns a if not empty, otherwise b +func IfNotEmptyOr(a, b string) string { + if a != "" { + return a + } + return b +} + +// If returns a if con is true, b otherwise +func If(con bool, a, b interface{}) interface{} { + if con { + return a + } + return b +} From 9cf3d5ae2021b08889c696f5725a2bd830511820 Mon Sep 17 00:00:00 2001 From: zachmann Date: Tue, 1 Jun 2021 13:24:38 +0200 Subject: [PATCH 10/61] add OIDC compaitibility for AT requests --- .../configuration/configurationEndpoint.go | 1 + .../configuration/pkg/mytokenConfiguration.go | 1 + .../token/access/accessTokenEndpoint.go | 6 ++++-- .../token/access/pkg/accessTokenRequest.go | 5 +++-- pkg/api/v0/accessTokenRequest.go | 12 ++++++------ shared/model/grantType.go | 16 ++++++++++++++-- 6 files changed, 29 insertions(+), 12 deletions(-) diff --git a/internal/endpoints/configuration/configurationEndpoint.go b/internal/endpoints/configuration/configurationEndpoint.go index 36dc3e5d..45307eb9 100644 --- a/internal/endpoints/configuration/configurationEndpoint.go +++ b/internal/endpoints/configuration/configurationEndpoint.go @@ -66,6 +66,7 @@ func basicConfiguration() *pkg.MytokenConfiguration { MytokenEndpointGrantTypesSupported: []pkgModel.GrantType{pkgModel.GrantTypeOIDCFlow, pkgModel.GrantTypeMytoken}, MytokenEndpointOIDCFlowsSupported: config.Get().Features.EnabledOIDCFlows, ResponseTypesSupported: []pkgModel.ResponseType{pkgModel.ResponseTypeToken}, + TokenEndpoint: utils.CombineURLPath(config.Get().IssuerURL, apiPaths.AccessTokenEndpoint), } } diff --git a/internal/endpoints/configuration/pkg/mytokenConfiguration.go b/internal/endpoints/configuration/pkg/mytokenConfiguration.go index 2232e9c3..846c88ab 100644 --- a/internal/endpoints/configuration/pkg/mytokenConfiguration.go +++ b/internal/endpoints/configuration/pkg/mytokenConfiguration.go @@ -13,4 +13,5 @@ type MytokenConfiguration struct { MytokenEndpointGrantTypesSupported []model.GrantType `json:"mytoken_endpoint_grant_types_supported"` MytokenEndpointOIDCFlowsSupported []model.OIDCFlow `json:"mytoken_endpoint_oidc_flows_supported"` ResponseTypesSupported []model.ResponseType `json:"response_types_supported"` + TokenEndpoint string `json:"token_endpoint"` // For compatibility with OIDC } diff --git a/internal/endpoints/token/access/accessTokenEndpoint.go b/internal/endpoints/token/access/accessTokenEndpoint.go index 4072a19d..802c4bfa 100644 --- a/internal/endpoints/token/access/accessTokenEndpoint.go +++ b/internal/endpoints/token/access/accessTokenEndpoint.go @@ -1,7 +1,6 @@ package access import ( - "encoding/json" "strings" "github.com/gofiber/fiber/v2" @@ -32,10 +31,13 @@ import ( func HandleAccessTokenEndpoint(ctx *fiber.Ctx) error { log.Debug("Handle access token request") req := request.AccessTokenRequest{} - if err := json.Unmarshal(ctx.Body(), &req); err != nil { + if err := ctx.BodyParser(&req); err != nil { return serverModel.ErrorToBadRequestErrorResponse(err).Send(ctx) } log.Trace("Parsed access token request") + if req.Mytoken == "" { + req.Mytoken = req.RefreshToken + } if req.GrantType != model.GrantTypeMytoken { res := serverModel.Response{ diff --git a/internal/endpoints/token/access/pkg/accessTokenRequest.go b/internal/endpoints/token/access/pkg/accessTokenRequest.go index 46bd79a1..8a2ecf99 100644 --- a/internal/endpoints/token/access/pkg/accessTokenRequest.go +++ b/internal/endpoints/token/access/pkg/accessTokenRequest.go @@ -9,6 +9,7 @@ import ( // AccessTokenRequest holds an request for an access token type AccessTokenRequest struct { api.AccessTokenRequest `json:",inline"` - GrantType model.GrantType `json:"grant_type"` - Mytoken token.Token `json:"mytoken"` + GrantType model.GrantType `json:"grant_type" xml:"grant_type" form:"grant_type"` + Mytoken token.Token `json:"mytoken" xml:"mytoken" form:"mytoken"` + RefreshToken token.Token `json:"refresh_token" xml:"refresh_token" form:"refresh_token"` } diff --git a/pkg/api/v0/accessTokenRequest.go b/pkg/api/v0/accessTokenRequest.go index eec84fdf..cd94c758 100644 --- a/pkg/api/v0/accessTokenRequest.go +++ b/pkg/api/v0/accessTokenRequest.go @@ -2,10 +2,10 @@ package api // AccessTokenRequest holds an request for an access token type AccessTokenRequest struct { - Issuer string `json:"oidc_issuer,omitempty"` - GrantType string `json:"grant_type"` - Mytoken string `json:"mytoken"` - Scope string `json:"scope,omitempty"` - Audience string `json:"audience,omitempty"` - Comment string `json:"comment,omitempty"` + Issuer string `json:"oidc_issuer,omitempty" form:"issuer" xml:"oidc_issuer"` + GrantType string `json:"grant_type" form:"grant_type" xml:"grant_type"` + Mytoken string `json:"mytoken" form:"mytoken" xml:"mytoken"` + Scope string `json:"scope,omitempty" form:"scope" xml:"scope"` + Audience string `json:"audience,omitempty" form:"audience" xml:"audience"` + Comment string `json:"comment,omitempty" form:"comment" xml:"comment"` } diff --git a/shared/model/grantType.go b/shared/model/grantType.go index 4c7d9e1e..4fb27739 100644 --- a/shared/model/grantType.go +++ b/shared/model/grantType.go @@ -4,9 +4,8 @@ import ( "encoding/json" "fmt" + "github.com/oidc-mytoken/server/pkg/api/v0" yaml "gopkg.in/yaml.v3" - - api "github.com/oidc-mytoken/server/pkg/api/v0" ) // GrantType is an enum like type for grant types @@ -33,6 +32,9 @@ func NewGrantType(s string) GrantType { return GrantType(i) } } + if s == "refresh_token" { // RT=MT compatibility + return GrantTypeMytoken + } return -1 } @@ -74,6 +76,16 @@ func (g *GrantType) UnmarshalJSON(data []byte) error { return nil } +// UnmarshalText implements the encoding.TextUnmarshaler interface +func (g *GrantType) UnmarshalText(data []byte) error { + s := string(data) + *g = NewGrantType(s) + if !g.Valid() { + return fmt.Errorf("value '%s' not valid for GrantType", s) + } + return nil +} + // MarshalJSON implements the json.Marshaler interface func (g GrantType) MarshalJSON() ([]byte, error) { return json.Marshal(g.String()) From 12a5d03c90d108eb7d3fa188567c712308d00776 Mon Sep 17 00:00:00 2001 From: zachmann Date: Thu, 10 Jun 2021 13:01:01 +0200 Subject: [PATCH 11/61] docker swarm --- .gitignore | 5 + .goreleaser.yml | 44 ++- cmd/mytoken-server/Dockerfile | 8 +- cmd/mytoken-server/main.go | 3 +- .../mytoken-dbGarbageCollector/Dockerfile | 5 - .../mytoken-migratedb/Dockerfile | 5 + cmd/mytoken-server/mytoken-migratedb/main.go | 132 +++++++++ .../mytoken-migratedb/version.go | 155 +++++++++++ cmd/mytoken-server/mytoken-setup/Dockerfile | 7 +- cmd/mytoken-server/mytoken-setup/setup.go | 250 +++++++++--------- config/example-config.yaml | 5 +- docker/docker-swarm.yaml | 167 ++++++++++++ docker/example-.env | 17 ++ docker/example-db.env | 9 + docker/example-docker-config.yaml | 146 ++++++++++ docker/haproxy/example-haproxy.cfg | 57 ++++ go.mod | 14 +- go.sum | 18 +- internal/config/config.go | 30 ++- internal/db/cluster/cluster.go | 18 +- internal/db/db.go | 7 +- internal/db/dbmigrate/migrate.go | 12 + .../dbdefinition.go => dbmigrate/v0.2.0.go} | 237 +++++++---------- .../dbrepo/authcodeinforepo/authcodeInfo.go | 2 +- internal/db/dbrepo/eventrepo/event.go | 2 +- internal/db/dbrepo/eventrepo/eventHistory.go | 2 +- internal/db/dbrepo/mytokenrepo/mytoken.go | 2 +- .../transfercoderepo/pollingCodeTmpST.go | 2 +- internal/db/dbrepo/mytokenrepo/tree/tree.go | 2 +- internal/db/dbrepo/versionrepo/versionrepo.go | 98 +++++++ .../configuration/configurationEndpoint.go | 2 +- .../configuration/pkg/mytokenConfiguration.go | 2 +- internal/endpoints/consent/consent.go | 2 +- internal/endpoints/consent/pkg/capability.go | 2 +- .../endpoints/consent/pkg/consentrequest.go | 2 +- .../revocation/revocationEndpoint.go | 2 +- .../token/access/accessTokenEndpoint.go | 2 +- .../token/access/pkg/accessTokenRequest.go | 2 +- .../token/mytoken/mytokenEndpoint.go | 2 +- .../token/mytoken/pkg/myTokenRequest.go | 2 +- .../token/mytoken/pkg/myTokenResponse.go | 2 +- .../token/mytoken/pkg/oidcFlowRequest.go | 2 +- .../token/mytoken/pkg/pollingCodeRequest.go | 2 +- .../token/mytoken/pkg/transferCodeRequest.go | 2 +- .../token/mytoken/pkg/transferCodeResponse.go | 2 +- .../token/mytoken/polling/pollingEndpoint.go | 2 +- .../token/mytoken/transferEndpoint.go | 2 +- internal/endpoints/tokeninfo/history.go | 2 +- internal/endpoints/tokeninfo/introspect.go | 2 +- internal/endpoints/tokeninfo/list.go | 2 +- .../tokeninfo/pkg/tokenIntrospectResponse.go | 2 +- .../tokeninfo/pkg/tokeninfoRequest.go | 2 +- internal/endpoints/tokeninfo/tree.go | 2 +- internal/model/response.go | 2 +- internal/oidc/authcode/authcode.go | 2 +- internal/server/errorHandler.go | 2 +- internal/server/server.go | 4 +- internal/utils/ctxUtils/networkData.go | 2 +- internal/utils/ctxUtils/token.go | 2 +- pkg/api/v0/accessTokenRequest.go | 11 - pkg/api/v0/accessTokenResponse.go | 10 - pkg/api/v0/apiError.go | 47 ---- pkg/api/v0/authCodeFlowResponse.go | 14 - pkg/api/v0/authcodeFlowRequest.go | 19 -- pkg/api/v0/capability.go | 148 ----------- pkg/api/v0/capability_test.go | 58 ---- pkg/api/v0/clientMetaData.go | 7 - pkg/api/v0/eventHistory.go | 10 - pkg/api/v0/grantTypes.go | 13 - pkg/api/v0/mytoken.go | 67 ----- pkg/api/v0/mytokenConfiguration.go | 28 -- pkg/api/v0/mytokenEntry.go | 15 -- pkg/api/v0/mytokenRequest.go | 14 - pkg/api/v0/mytokenResponse.go | 12 - pkg/api/v0/oidcFlow.go | 7 - pkg/api/v0/pollingCodeRequest.go | 7 - pkg/api/v0/responseType.go | 8 - pkg/api/v0/restrictions.go | 24 -- pkg/api/v0/revocationRequest.go | 8 - pkg/api/v0/tokeninfoAction.go | 12 - pkg/api/v0/tokeninfoRequest.go | 6 - pkg/api/v0/tokeninfoResponses.go | 17 -- pkg/api/v0/transfercodeRequest.go | 12 - pkg/api/v0/transfercodeResponse.go | 8 - shared/model/apiError.go | 2 +- shared/model/grantType.go | 2 +- shared/model/oidcFlow.go | 2 +- shared/model/responseType.go | 2 +- shared/model/tokeninfoAction.go | 2 +- shared/mytoken/event/event.go | 2 +- shared/mytoken/mytokenHandler.go | 2 +- shared/mytoken/pkg/mytoken.go | 2 +- shared/mytoken/restrictions/restriction.go | 2 +- .../mytoken/restrictions/restriction_test.go | 2 +- shared/mytoken/rotation/rotation.go | 2 +- 95 files changed, 1172 insertions(+), 953 deletions(-) create mode 100644 cmd/mytoken-server/mytoken-migratedb/Dockerfile create mode 100644 cmd/mytoken-server/mytoken-migratedb/main.go create mode 100644 cmd/mytoken-server/mytoken-migratedb/version.go create mode 100644 docker/docker-swarm.yaml create mode 100644 docker/example-.env create mode 100644 docker/example-db.env create mode 100644 docker/example-docker-config.yaml create mode 100644 docker/haproxy/example-haproxy.cfg create mode 100644 internal/db/dbmigrate/migrate.go rename internal/db/{dbdefinition/dbdefinition.go => dbmigrate/v0.2.0.go} (85%) create mode 100644 internal/db/dbrepo/versionrepo/versionrepo.go delete mode 100644 pkg/api/v0/accessTokenRequest.go delete mode 100644 pkg/api/v0/accessTokenResponse.go delete mode 100644 pkg/api/v0/apiError.go delete mode 100644 pkg/api/v0/authCodeFlowResponse.go delete mode 100644 pkg/api/v0/authcodeFlowRequest.go delete mode 100644 pkg/api/v0/capability.go delete mode 100644 pkg/api/v0/capability_test.go delete mode 100644 pkg/api/v0/clientMetaData.go delete mode 100644 pkg/api/v0/eventHistory.go delete mode 100644 pkg/api/v0/grantTypes.go delete mode 100644 pkg/api/v0/mytoken.go delete mode 100644 pkg/api/v0/mytokenConfiguration.go delete mode 100644 pkg/api/v0/mytokenEntry.go delete mode 100644 pkg/api/v0/mytokenRequest.go delete mode 100644 pkg/api/v0/mytokenResponse.go delete mode 100644 pkg/api/v0/oidcFlow.go delete mode 100644 pkg/api/v0/pollingCodeRequest.go delete mode 100644 pkg/api/v0/responseType.go delete mode 100644 pkg/api/v0/restrictions.go delete mode 100644 pkg/api/v0/revocationRequest.go delete mode 100644 pkg/api/v0/tokeninfoAction.go delete mode 100644 pkg/api/v0/tokeninfoRequest.go delete mode 100644 pkg/api/v0/tokeninfoResponses.go delete mode 100644 pkg/api/v0/transfercodeRequest.go delete mode 100644 pkg/api/v0/transfercodeResponse.go diff --git a/.gitignore b/.gitignore index 98eb4b90..56f6bfc7 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,8 @@ IP2LOCATION-LITE-DB1.IPV6.BIN /cmd/test generateDDL.sh dist/ +/mytoken-migratedb +/docker/docker-compose.yaml +/docker/db.env +/docker/haproxy/haproxy.cfg +/docker/.env diff --git a/.goreleaser.yml b/.goreleaser.yml index 8ec49a71..a3d33998 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -4,7 +4,7 @@ before: - go mod tidy builds: - id: server - main: ./cmd/mytoken-server/main.go + main: ./cmd/mytoken-server binary: mytoken-server env: - CGO_ENABLED=0 @@ -13,14 +13,21 @@ builds: # - windows # - darwin - id: setup - main: ./cmd/mytoken-server/mytoken-setup/setup.go + main: ./cmd/mytoken-server/mytoken-setup binary: mytoken-setup env: - CGO_ENABLED=0 goos: - linux + - id: migratedb + main: ./cmd/mytoken-server/mytoken-migratedb + binary: mytoken-migratedb + env: + - CGO_ENABLED=0 + goos: + - linux - id: garbage - main: ./cmd/mytoken-server/mytoken-dbGarbageCollector/main.go + main: ./cmd/mytoken-server/mytoken-dbGarbageCollector binary: mytoken-dbgc env: - CGO_ENABLED=0 @@ -71,6 +78,20 @@ nfpms: release: 1 section: misc bindir: /usr/bin + - id: migratedb-pkg + package_name: mytoken-server-migratedb + builds: + - migratedb + homepage: https://mytoken-doc.data.kit.edu/server/intro + maintainer: Gabriel Zachmann + description: A tool for migrating the database between versions + license: MIT + formats: + - deb + - rpm + release: 1 + section: misc + bindir: /usr/bin - id: garbage-pkg package_name: mytoken-server-dbgc builds: @@ -120,6 +141,23 @@ dockers: - "--label=org.opencontainers.image.title=mytoken-setup" - "--label=org.opencontainers.image.revision={{.FullCommit}}" - "--label=org.opencontainers.image.version={{.Version}}" + - goos: linux + goarch: amd64 + ids: + - migratedb + image_templates: + - "oidcmytoken/mytoken-migratedb:latest" + - "oidcmytoken/mytoken-migratedb:{{ .Tag }}" + - "oidcmytoken/mytoken-migratedb:v{{ .Major }}" + - "oidcmytoken/mytoken-migratedb:v{{ .Major }}.{{ .Minor }}" + skip_push: true + dockerfile: cmd/mytoken-server/mytoken-migratedb/Dockerfile + build_flag_templates: + - "--pull" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.title=mytoken-migratedb" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" - goos: linux goarch: amd64 ids: diff --git a/cmd/mytoken-server/Dockerfile b/cmd/mytoken-server/Dockerfile index a5b5a184..f064a9c4 100644 --- a/cmd/mytoken-server/Dockerfile +++ b/cmd/mytoken-server/Dockerfile @@ -1,9 +1,5 @@ -FROM debian:stable -RUN apt-get update && \ - apt-get install ca-certificates -y && \ - apt-get autoremove -y && \ - apt-get clean -y && \ - rm -rf /var/lib/apt/lists/* +FROM oidcmytoken/debian-wait-for:latest WORKDIR /mytoken COPY mytoken-server /usr/bin/mytoken-server +ENTRYPOINT ["/opt/mytoken/scripts/run.sh"] CMD ["mytoken-server"] diff --git a/cmd/mytoken-server/main.go b/cmd/mytoken-server/main.go index 4c0d5f41..89432d57 100644 --- a/cmd/mytoken-server/main.go +++ b/cmd/mytoken-server/main.go @@ -5,6 +5,7 @@ import ( "os/signal" "syscall" + "github.com/oidc-mytoken/server/internal/db/dbrepo/versionrepo" log "github.com/sirupsen/logrus" "github.com/oidc-mytoken/server/internal/config" @@ -25,7 +26,7 @@ func main() { server.Init() configurationEndpoint.Init() authcode.Init() - db.Connect() + versionrepo.ConnectToVersion() jws.LoadKey() httpClient.Init(config.Get().IssuerURL) geoip.Init() diff --git a/cmd/mytoken-server/mytoken-dbGarbageCollector/Dockerfile b/cmd/mytoken-server/mytoken-dbGarbageCollector/Dockerfile index 7104472f..1dea1c03 100644 --- a/cmd/mytoken-server/mytoken-dbGarbageCollector/Dockerfile +++ b/cmd/mytoken-server/mytoken-dbGarbageCollector/Dockerfile @@ -1,9 +1,4 @@ FROM debian:stable -RUN apt-get update && \ - apt-get install ca-certificates -y && \ - apt-get autoremove -y && \ - apt-get clean -y && \ - rm -rf /var/lib/apt/lists/* WORKDIR /mytoken COPY mytoken-dbgc /usr/bin/mytoken-dbgc CMD ["mytoken-dbgc"] diff --git a/cmd/mytoken-server/mytoken-migratedb/Dockerfile b/cmd/mytoken-server/mytoken-migratedb/Dockerfile new file mode 100644 index 00000000..a0cd0e22 --- /dev/null +++ b/cmd/mytoken-server/mytoken-migratedb/Dockerfile @@ -0,0 +1,5 @@ +FROM oidcmytoken/debian-wait-for:latest +WORKDIR /mytoken +COPY mytoken-migratedb /usr/bin/mytoken-migratedb +ENTRYPOINT ["/opt/mytoken/scripts/run.sh"] +CMD ["mytoken-migratedb"] diff --git a/cmd/mytoken-server/mytoken-migratedb/main.go b/cmd/mytoken-server/mytoken-migratedb/main.go new file mode 100644 index 00000000..584819ea --- /dev/null +++ b/cmd/mytoken-server/mytoken-migratedb/main.go @@ -0,0 +1,132 @@ +package main + +import ( + "fmt" + "os" + "strings" + "time" + + "github.com/oidc-mytoken/server/internal/config" + "github.com/oidc-mytoken/server/internal/db" + "github.com/oidc-mytoken/server/shared/utils/fileutil" + "github.com/zachmann/cli/v2" + "golang.org/x/term" + + "github.com/oidc-mytoken/server/internal/model/version" + log "github.com/sirupsen/logrus" +) + +var configFile string +var force bool + +var dbConfig struct { + config.DBConf + Hosts cli.StringSlice +} + +var app = &cli.App{ + Name: "mytoken-migratedb", + Usage: "Command line client for easy database migration between mytoken versions", + Version: version.VERSION(), + Compiled: time.Time{}, + Authors: []*cli.Author{{ + Name: "Gabriel Zachmann", + Email: "gabriel.zachmann@kit.edu", + }}, + Copyright: "Karlsruhe Institute of Technology 2020-2021", + UseShortOptionHandling: true, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "nodes", + Aliases: []string{"n", "s", "server"}, + Usage: "The passed file lists the mytoken nodes / servers (one server per line)", + EnvVars: []string{"MYTOKEN_NODES_FILE"}, + TakesFile: true, + Placeholder: "FILE", + Destination: &configFile, + }, + &cli.BoolFlag{ + Name: "force", + Aliases: []string{"f"}, + Usage: "Force a complete database migration. Mytoken servers are not checked if they are compatible with the changes.", + Destination: &force, + HideDefaultValue: true, + }, + + &cli.StringFlag{ + Name: "db", + Usage: "The name of the database", + EnvVars: []string{"DB_DATABASE"}, + Value: "mytoken", + Destination: &dbConfig.DB, + Placeholder: "DB", + }, + &cli.StringFlag{ + Name: "user", + Aliases: []string{"u"}, + Usage: "The user for connecting to the database (Needs correct privileges)", + EnvVars: []string{"DB_USER"}, + Value: "root", + Destination: &dbConfig.User, + Placeholder: "USER", + }, + &cli.StringFlag{ + Name: "password", + Aliases: []string{"p"}, + Usage: "The password for connecting to the database", + EnvVars: []string{"DB_PASSWORD"}, + Destination: &dbConfig.Password, + Placeholder: "PASSWORD", + }, + &cli.StringFlag{ + Name: "password-file", + Aliases: []string{"pass-file"}, + Usage: "Read the password for connecting to the database from this file", + EnvVars: []string{"DB_PASSWORD_FILE"}, + Destination: &dbConfig.PasswordFile, + Placeholder: "FILE", + }, + &cli.StringSliceFlag{ + Name: "host", + Aliases: []string{"hosts"}, + Usage: "The hostnames of the database nodes", + EnvVars: []string{"DB_HOST", "DB_HOSTS", "DB_NODES"}, + Value: cli.NewStringSlice("localhost"), + Destination: &dbConfig.Hosts, + Placeholder: "HOST", + }, + }, + Action: func(context *cli.Context) error { + mytokenNodes := []string{} + if context.Args().Len() > 0 { + mytokenNodes = context.Args().Slice() + } else if configFile != "" { + mytokenNodes = readConfigFile(configFile) + } else if os.Getenv("MYTOKEN_NODES") != "" { + mytokenNodes = strings.Split(os.Getenv("MYTOKEN_NODES"), ",") + } else if !force { + return fmt.Errorf("No mytoken servers specified. Please provide mytoken servers or use '-f' to force database migration.") + } + dbConfig.ReconnectInterval = 60 + dbConfig.DBConf.Hosts = dbConfig.Hosts.Value() + db.ConnectConfig(dbConfig.DBConf) + return migrateDB(nil, mytokenNodes) + }, +} + +func readConfigFile(file string) []string { + data := string(fileutil.MustReadFile(file)) + return strings.Split(data, "\n") +} + +func main() { + + termWidth, _, err := term.GetSize(int(os.Stdout.Fd())) + if err == nil { + cli.HelpWrapAt = termWidth + } + + if err = app.Run(os.Args); err != nil { + log.Fatal(err) + } +} diff --git a/cmd/mytoken-server/mytoken-migratedb/version.go b/cmd/mytoken-server/mytoken-migratedb/version.go new file mode 100644 index 00000000..481165cb --- /dev/null +++ b/cmd/mytoken-server/mytoken-migratedb/version.go @@ -0,0 +1,155 @@ +package main + +import ( + "strings" + "time" + + "github.com/jmoiron/sqlx" + mytokenlib "github.com/oidc-mytoken/lib" + "github.com/oidc-mytoken/server/internal/db" + "github.com/oidc-mytoken/server/internal/db/dbmigrate" + "github.com/oidc-mytoken/server/internal/db/dbrepo/versionrepo" + "github.com/oidc-mytoken/server/internal/model/version" + log "github.com/sirupsen/logrus" +) + +func did(state versionrepo.DBVersionState, version string) (beforeDone bool, afterDone bool) { + for _, entry := range state { + if entry.Version == version { + if entry.Before.Valid { + beforeDone = true + } + if entry.After.Valid { + afterDone = true + } + return + } + } + return +} + +func getDoneMap(state versionrepo.DBVersionState, versions dbmigrate.VersionCommands) (map[string]bool, map[string]bool) { + before := make(map[string]bool, len(versions)) + after := make(map[string]bool, len(versions)) + for v := range versions { + before[v], after[v] = did(state, v) + } + return before, after +} + +func migrateDB(tx *sqlx.Tx, mytokenNodes []string) error { + v := version.VERSION() + return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { + dbState, err := versionrepo.GetVersionState(tx) + if err != nil { + return err + } + return runUpdates(tx, dbState, mytokenNodes, v) + }) +} + +func runUpdates(tx *sqlx.Tx, dbState versionrepo.DBVersionState, mytokenNodes []string, version string) error { + cmds := dbmigrate.Migrate + beforeDone, afterDone := getDoneMap(dbState, cmds) + if err := db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { + return runBeforeUpdates(tx, cmds, beforeDone) + }); err != nil { + return err + } + if !anyAfterUpdates(cmds, afterDone) { // If there are no after cmds to run, we are done + return nil + } + waitUntilAllNodesOnVersion(mytokenNodes, version) + + return db.RunWithinTransaction(nil, func(tx *sqlx.Tx) error { + return runAfterUpdates(tx, cmds, afterDone) + }) +} + +func runBeforeUpdates(tx *sqlx.Tx, cmds dbmigrate.VersionCommands, beforeDone map[string]bool) error { + return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { + for version, cs := range cmds { + if err := updateCallback(tx, cs.Before, version, beforeDone, versionrepo.SetVersionBefore); err != nil { + return err + } + } + return nil + }) +} +func anyAfterUpdates(cmds dbmigrate.VersionCommands, afterDone map[string]bool) bool { + for version, cs := range cmds { + if len(cs.After) > 0 && !afterDone[version] { + return true + } + } + return false +} +func runAfterUpdates(tx *sqlx.Tx, cmds dbmigrate.VersionCommands, afterDone map[string]bool) error { + return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { + for version, cs := range cmds { + if err := updateCallback(tx, cs.After, version, afterDone, versionrepo.SetVersionAfter); err != nil { + return err + } + } + return nil + }) +} +func updateCallback(tx *sqlx.Tx, cmds []string, version string, done map[string]bool, dbUpdateCallback func(*sqlx.Tx, string) error) error { + if len(cmds) == 0 { + return nil + } + if done[version] { + return nil + } + return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { + if err := runDBCommands(tx, cmds); err != nil { + return err + } + return dbUpdateCallback(tx, version) + }) +} + +func waitUntilAllNodesOnVersion(mytokenNodes []string, version string) { + allNodesOnVersion := len(mytokenNodes) == 0 + for !allNodesOnVersion { + tmp := []string{} + for _, n := range mytokenNodes { + v, err := getVersionForNode(n) + if err != nil { + log.WithError(err).Error() + } + if v != version { + tmp = append(tmp, n) + } + } + mytokenNodes = tmp + allNodesOnVersion = len(mytokenNodes) == 0 + time.Sleep(60 * time.Second) + } +} + +func getVersionForNode(node string) (string, error) { + if !strings.HasPrefix(node, "http") { + node = "https://" + node + } + my, err := mytokenlib.NewMytokenProvider(node) + if err != nil { + return "", err + } + return my.Version, nil +} + +func runDBCommands(tx *sqlx.Tx, cmds []string) error { + return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { + for _, cmd := range cmds { + cmd = strings.TrimSpace(cmd) + if cmd != "" && !strings.HasPrefix(cmd, "--") { + log.Trace(cmd) + if _, err := tx.Exec(cmd); err != nil { + return err + } + } + } + return nil + }) +} diff --git a/cmd/mytoken-server/mytoken-setup/Dockerfile b/cmd/mytoken-server/mytoken-setup/Dockerfile index 0ac566f4..8e3a424b 100644 --- a/cmd/mytoken-server/mytoken-setup/Dockerfile +++ b/cmd/mytoken-server/mytoken-setup/Dockerfile @@ -1,9 +1,4 @@ -FROM debian:stable -RUN apt-get update && \ - apt-get install ca-certificates -y && \ - apt-get autoremove -y && \ - apt-get clean -y && \ - rm -rf /var/lib/apt/lists/* +FROM oidcmytoken/debian-wait-for:latest WORKDIR /mytoken COPY mytoken-setup /usr/bin/mytoken-setup ENTRYPOINT ["mytoken-setup"] diff --git a/cmd/mytoken-server/mytoken-setup/setup.go b/cmd/mytoken-server/mytoken-setup/setup.go index ee872d34..a139330a 100644 --- a/cmd/mytoken-server/mytoken-setup/setup.go +++ b/cmd/mytoken-server/mytoken-setup/setup.go @@ -1,27 +1,19 @@ package main import ( - "database/sql" "errors" "fmt" "io/ioutil" "os" - "strings" "github.com/Songmu/prompter" flags "github.com/jessevdk/go-flags" - "github.com/jmoiron/sqlx" log "github.com/sirupsen/logrus" "github.com/oidc-mytoken/server/internal/config" - "github.com/oidc-mytoken/server/internal/db/cluster" - "github.com/oidc-mytoken/server/internal/db/dbdefinition" "github.com/oidc-mytoken/server/internal/jws" - "github.com/oidc-mytoken/server/internal/model" loggerUtils "github.com/oidc-mytoken/server/internal/utils/logger" "github.com/oidc-mytoken/server/internal/utils/zipdownload" - model2 "github.com/oidc-mytoken/server/shared/model" - event "github.com/oidc-mytoken/server/shared/mytoken/event/pkg" "github.com/oidc-mytoken/server/shared/utils/fileutil" ) @@ -106,124 +98,124 @@ func (c *commandGenSigningKey) Execute(args []string) error { return nil } -// Execute implements the flags.Commander interface -func (c *commandCreateDB) Execute(args []string) error { - password := "" - if c.Password != nil && *c.Password == "" { // -p specified without argument - password = prompter.Password("Database Password") - } - db := cluster.NewFromConfig(config.DBConf{ - Hosts: config.Get().DB.Hosts, - User: c.Username, - Password: password, - }) - if err := checkDB(db); err != nil { - return err - } - err := db.Transact(func(tx *sqlx.Tx) error { - if err := createDB(tx); err != nil { - return err - } - if err := createUser(tx); err != nil { - return err - } - if err := createTables(tx); err != nil { - return err - } - if err := addPredefinedValues(tx); err != nil { // skipcq RVV-B0005 - return err - } - return nil - }) - if err == nil { - fmt.Println("Prepared database.") - } - return err -} - -func addPredefinedValues(tx *sqlx.Tx) error { - for _, attr := range model.Attributes { - if _, err := tx.Exec(`INSERT IGNORE INTO Attributes (attribute) VALUES(?)`, attr); err != nil { - return err - } - } - log.WithField("database", config.Get().DB.DB).Debug("Added attribute values") - for _, evt := range event.AllEvents { - if _, err := tx.Exec(`INSERT IGNORE INTO Events (event) VALUES(?)`, evt); err != nil { - return err - } - } - log.WithField("database", config.Get().DB.DB).Debug("Added event values") - for _, grt := range model2.AllGrantTypes { - if _, err := tx.Exec(`INSERT IGNORE INTO Grants (grant_type) VALUES(?)`, grt); err != nil { - return err - } - } - log.WithField("database", config.Get().DB.DB).Debug("Added grant_type values") - return nil -} - -func createTables(tx *sqlx.Tx) error { - if _, err := tx.Exec(`USE ` + config.Get().DB.DB); err != nil { - return err - } - for _, cmd := range dbdefinition.DDL { - cmd = strings.TrimSpace(cmd) - if cmd != "" && !strings.HasPrefix(cmd, "--") { - log.Trace(cmd) - if _, err := tx.Exec(cmd); err != nil { - return err - } - } - } - log.WithField("database", config.Get().DB.DB).Debug("Created tables") - return nil -} - -func createDB(tx *sqlx.Tx) error { - if _, err := tx.Exec(`DROP DATABASE IF EXISTS ` + config.Get().DB.DB); err != nil { - return err - } - log.WithField("database", config.Get().DB.DB).Debug("Dropped database") - if _, err := tx.Exec(`CREATE DATABASE ` + config.Get().DB.DB); err != nil { - return err - } - log.WithField("database", config.Get().DB.DB).Debug("Created database") - return nil -} - -func createUser(tx *sqlx.Tx) error { - log.WithField("user", config.Get().DB.User).Debug("Creating user") - if _, err := tx.Exec(`CREATE USER IF NOT EXISTS '` + config.Get().DB.User + `' IDENTIFIED BY '` + config.Get().DB.Password + `'`); err != nil { - return err - } - log.WithField("user", config.Get().DB.User).Debug("Created user") - if _, err := tx.Exec(`GRANT INSERT, UPDATE, DELETE, SELECT ON ` + config.Get().DB.DB + `.* TO '` + config.Get().DB.User + `'`); err != nil { - return err - } - if _, err := tx.Exec(`FLUSH PRIVILEGES `); err != nil { - return err - } - log.WithField("user", config.Get().DB.User).WithField("database", config.Get().DB.DB).Debug("Granted privileges") - return nil -} - -func checkDB(db *cluster.Cluster) error { - log.WithField("database", config.Get().DB.DB).Debug("Check if database already exists") - var rows *sql.Rows - if err := db.Transact(func(tx *sqlx.Tx) error { - var err error - rows, err = tx.Query(`SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME=?`, config.Get().DB.DB) - return err - }); err != nil { - return err - } - defer rows.Close() - if rows.Next() { - if !prompter.YesNo("The database already exists. If we continue all data will be deleted. Do you want to continue?", false) { - _ = rows.Close() - os.Exit(1) // skipcq CRT-D0011 - } - } - return nil -} +// // Execute implements the flags.Commander interface +// func (c *commandCreateDB) Execute(args []string) error { +// password := "" +// if c.Password != nil && *c.Password == "" { // -p specified without argument +// password = prompter.Password("Database Password") +// } +// db := cluster.NewFromConfig(config.DBConf{ +// Hosts: config.Get().DB.Hosts, +// User: c.Username, +// Password: password, +// }) +// if err := checkDB(db); err != nil { +// return err +// } +// err := db.Transact(func(tx *sqlx.Tx) error { +// if err := createDB(tx); err != nil { +// return err +// } +// if err := createUser(tx); err != nil { +// return err +// } +// if err := createTables(tx); err != nil { +// return err +// } +// if err := addPredefinedValues(tx); err != nil { // skipcq RVV-B0005 +// return err +// } +// return nil +// }) +// if err == nil { +// fmt.Println("Prepared database.") +// } +// return err +// } +// +// func addPredefinedValues(tx *sqlx.Tx) error { +// for _, attr := range model.Attributes { +// if _, err := tx.Exec(`INSERT IGNORE INTO Attributes (attribute) VALUES(?)`, attr); err != nil { +// return err +// } +// } +// log.WithField("database", config.Get().DB.DB).Debug("Added attribute values") +// for _, evt := range event.AllEvents { +// if _, err := tx.Exec(`INSERT IGNORE INTO Events (event) VALUES(?)`, evt); err != nil { +// return err +// } +// } +// log.WithField("database", config.Get().DB.DB).Debug("Added event values") +// for _, grt := range model2.AllGrantTypes { +// if _, err := tx.Exec(`INSERT IGNORE INTO Grants (grant_type) VALUES(?)`, grt); err != nil { +// return err +// } +// } +// log.WithField("database", config.Get().DB.DB).Debug("Added grant_type values") +// return nil +// } +// +// func createTables(tx *sqlx.Tx) error { +// if _, err := tx.Exec(`USE ` + config.Get().DB.DB); err != nil { +// return err +// } +// for _, cmd := range dbmigrate.DDL { +// cmd = strings.TrimSpace(cmd) +// if cmd != "" && !strings.HasPrefix(cmd, "--") { +// log.Trace(cmd) +// if _, err := tx.Exec(cmd); err != nil { +// return err +// } +// } +// } +// log.WithField("database", config.Get().DB.DB).Debug("Created tables") +// return nil +// } +// +// func createDB(tx *sqlx.Tx) error { +// if _, err := tx.Exec(`DROP DATABASE IF EXISTS ` + config.Get().DB.DB); err != nil { +// return err +// } +// log.WithField("database", config.Get().DB.DB).Debug("Dropped database") +// if _, err := tx.Exec(`CREATE DATABASE ` + config.Get().DB.DB); err != nil { +// return err +// } +// log.WithField("database", config.Get().DB.DB).Debug("Created database") +// return nil +// } +// +// func createUser(tx *sqlx.Tx) error { +// log.WithField("user", config.Get().DB.User).Debug("Creating user") +// if _, err := tx.Exec(`CREATE USER IF NOT EXISTS '` + config.Get().DB.User + `' IDENTIFIED BY '` + config.Get().DB.Password + `'`); err != nil { +// return err +// } +// log.WithField("user", config.Get().DB.User).Debug("Created user") +// if _, err := tx.Exec(`GRANT INSERT, UPDATE, DELETE, SELECT ON ` + config.Get().DB.DB + `.* TO '` + config.Get().DB.User + `'`); err != nil { +// return err +// } +// if _, err := tx.Exec(`FLUSH PRIVILEGES `); err != nil { +// return err +// } +// log.WithField("user", config.Get().DB.User).WithField("database", config.Get().DB.DB).Debug("Granted privileges") +// return nil +// } +// +// func checkDB(db *cluster.Cluster) error { +// log.WithField("database", config.Get().DB.DB).Debug("Check if database already exists") +// var rows *sql.Rows +// if err := db.Transact(func(tx *sqlx.Tx) error { +// var err error +// rows, err = tx.Query(`SELECT SCHEMA_NAME FROM INFORMATION_SCHEMA.SCHEMATA WHERE SCHEMA_NAME=?`, config.Get().DB.DB) +// return err +// }); err != nil { +// return err +// } +// defer rows.Close() +// if rows.Next() { +// if !prompter.YesNo("The database already exists. If we continue all data will be deleted. Do you want to continue?", false) { +// _ = rows.Close() +// os.Exit(1) // skipcq CRT-D0011 +// } +// } +// return nil +// } diff --git a/config/example-config.yaml b/config/example-config.yaml index d1122b78..aa8fdf14 100644 --- a/config/example-config.yaml +++ b/config/example-config.yaml @@ -13,7 +13,6 @@ service_operator: # Configuration for the mytoken server server: - hostname: "mytoken.example.com" # If TLS is not enabled, mytoken will listen on this port, default: 8000 port: 8000 tls: @@ -25,6 +24,8 @@ server: cert: # The TLS certificate key file key: + # If behind a load balancer or reverse proxy, set this option. Mytoken will read the client's ip address from this header. + # proxy_header: "X-FORWARDED-FOR" # The database file for ip geo location. Will be installed by setup to this location. geo_ip_db_file: "/IP2LOCATION-LITE-DB1.IPV6.BIN" @@ -40,6 +41,8 @@ database: - "localhost" user: "mytoken" password: "mytoken" + # Read the db password from this file + password_file: db: "mytoken" # The interval (in seconds) in which mytoken tries to reconnect to db nodes that are down try_reconnect_interval: 60 diff --git a/docker/docker-swarm.yaml b/docker/docker-swarm.yaml new file mode 100644 index 00000000..2ebcdc1f --- /dev/null +++ b/docker/docker-swarm.yaml @@ -0,0 +1,167 @@ +version: "3.9" +services: + mytoken: + hostname: 'mytoken_{{.Task.Slot}}' +# depends_on: +# - db + image: oidcmytoken/mytoken-server + working_dir: /root/mytoken + volumes: + - ${MYTOKEN_CONFIG_FILE}:/etc/mytoken/config.yaml + - ${MYTOKEN_GEOIPDB}:/root/mytoken/IP2LOCATION-LITE-DB1.IPV6.BIN + - ${MYTOKEN_LOGS_DIR}:/root/mytoken/logs + secrets: + - signing_key + - db_password + networks: + - frontend + - backend + environment: + - WAIT_FOR_NODES=db_1:3306,db_2:3306,db_3:3306 + deploy: + mode: replicated + replicas: 3 +# placement: + # max_replicas_per_node: 1 + restart_policy: + condition: on-failure + delay: 120s + window: 60s + rollback_config: + parallelism: 1 + delay: 120s + update_config: + parallelism: 1 + delay: 180s + failure_action: rollback + + db-bootstrap: + image: bitnami/mariadb-galera:latest + hostname: db-bootstrap + volumes: + - '${DB_BOOTSTRAP_DIR}:/bitnami/mariadb' + networks: + - backend + env_file: + - db.env + environment: + - MARIADB_ROOT_PASSWORD_FILE=/run/secrets/db_root_password + - MARIADB_PASSWORD_FILE=/run/secrets/db_password + - MARIADB_GALERA_MARIABACKUP_PASSWORD_FILE=/run/secrets/db_backup_password + - MARIADB_REPLICATION_PASSWORD_FILE=/run/secrets/db_replication_password + secrets: + - db_password + - db_root_password + - db_backup_password + - db_replication_password + deploy: + mode: replicated + replicas: 1 + restart_policy: + condition: none + update_config: + parallelism: 0 + healthcheck: + test: [ 'CMD', '/opt/bitnami/scripts/mariadb-galera/healthcheck.sh' ] + interval: 15s + timeout: 5s + retries: 6 + + db: + image: oidcmytoken/galera-node:latest + hostname: 'db_{{.Task.Slot}}' + volumes: + - '${DB_DIR_TEMPLATE}:/bitnami/mariadb' + networks: + - backend + env_file: + - db.env + environment: + - MARIADB_GALERA_CLUSTER_ADDRESS=gcomm://db-bootstrap,db_1,db_2,db_3 + - MARIADB_GALERA_MARIABACKUP_PASSWORD_FILE=/run/secrets/db_backup_password + - MARIADB_REPLICATION_PASSWORD_FILE=/run/secrets/db_replication_password + - WAIT_FOR_NODES=db-bootstrap:3306,db_1:3306,db_2:3306,db_3:3306 + secrets: + - db_backup_password + - db_replication_password + deploy: + mode: replicated + replicas: 3 + # placement: + # max_replicas_per_node: 1 + restart_policy: + condition: on-failure + rollback_config: + parallelism: 1 + delay: 120s + update_config: + parallelism: 1 + delay: 180s + failure_action: rollback + healthcheck: + test: [ 'CMD', '/opt/bitnami/scripts/mariadb-galera/healthcheck.sh' ] + interval: 15s + timeout: 5s + retries: 6 + command: ["/opt/bitnami/scripts/mariadb-galera/entrypoint.sh", "/opt/bitnami/scripts/mariadb-galera/run.sh"] + + migrate: + image: oidcmytoken/mytoken-migratedb +# depends_on: +# - db + networks: + - backend + environment: + - DB_PASSWORD=password + - DB_PASSWORD_FILE=/run/secrets/db_root_password + - DB_DATABASE=mytoken + - DB_NODES=db_1,db_2,db_3 + - WAIT_FOR_NODES=db_1:3306,db_2:3306,db_3:3306 + - MYTOKEN_NODES=mytoken_1,mytoken_2,mytoken_3 + secrets: + - db_root_password + deploy: + mode: replicated + replicas: 1 + restart_policy: + condition: on-failure + + load_balancer: + image: haproxy + networks: + - frontend + ports: + - target: 80 + published: 80 + mode: host + - target: 443 + published: 443 + mode: host + sysctls: + - net.ipv4.ip_unprivileged_port_start=0 + volumes: + - ${HAPROXY_DIR}:/usr/local/etc/haproxy:ro + secrets: + - cert + deploy: + mode: replicated + replicas: 1 + + +networks: + frontend: + backend: + +secrets: + cert: + file: ${SECRET_CERT} + signing_key: + file: ${SECRET_SIGNING_KEY} + db_password: + file: ${SECRET_DB_PASSWORD_FILE} + db_root_password: + file: ${SECRET_DB_ROOT_PASSWORD_FILE} + db_backup_password: + file: ${SECRET_DB_BACKUP_PASSWORD_FILE} + db_replication_password: + file: ${SECRET_DB_REPLICATION_PASSWORD_FILE} diff --git a/docker/example-.env b/docker/example-.env new file mode 100644 index 00000000..1f5a9ec4 --- /dev/null +++ b/docker/example-.env @@ -0,0 +1,17 @@ +COMPOSE_PROJECT_NAME=mytoken + +MYTOKEN_CONFIG_FILE=/home/mytoken/mytoken/config.yaml +MYTOKEN_GEOIPDB=/home/mytoken/mytoken/IP2LOCATION-LITE-DB1.IPV6.BIN +MYTOKEN_LOGS_DIR=/home/mytoken/mytoken/logs + +HAPROXY_DIR=/home/mytoken/mytoken/haproxy + +DB_BOOTSTRAP_DIR=/home/mytoken/mytoken/mytoken-db/b1 +DB_DIR_TEMPLATE=/home/mytoken/mytoken/mytoken-db/{{.Task.Slot}} + +SECRET_CERT=/home/mytoken/mytoken/mytoken.pem +SECRET_SIGNING_KEY=/home/mytoken/mytoken/mytoken.ecdsa.key +SECRET_DB_ROOT_PASSWORD_FILE=/home/mytoken/mytoken/db_root_password +SECRET_DB_PASSWORD_FILE=/home/mytoken/mytoken/db_password +SECRET_DB_REPLICATION_PASSWORD_FILE=/home/mytoken/mytoken/db_replication_password +SECRET_DB_BACKUP_PASSWORD_FILE=/home/mytoken/mytoken/db_backup_password diff --git a/docker/example-db.env b/docker/example-db.env new file mode 100644 index 00000000..05b55fc3 --- /dev/null +++ b/docker/example-db.env @@ -0,0 +1,9 @@ +MARIADB_GALERA_CLUSTER_NAME=mytoken +MARIADB_ROOT_PASSWORD=password +MARIADB_DATABASE=mytoken +MARIADB_USER=mytoken +MARIADB_PASSWORD=mytoken +MARIADB_GALERA_MARIABACKUP_USER=mariabackup_user +MARIADB_GALERA_MARIABACKUP_PASSWORD=mariabackup +MARIADB_REPLICATION_USER=replication_user +MARIADB_REPLICATION_PASSWORD=replication \ No newline at end of file diff --git a/docker/example-docker-config.yaml b/docker/example-docker-config.yaml new file mode 100644 index 00000000..a365cb86 --- /dev/null +++ b/docker/example-docker-config.yaml @@ -0,0 +1,146 @@ +# The issuer url to be used. MUST point to this server +issuer: "https://mytoken.example.com" + +# Details about the service operator. Will be displayed in the privacy page. +service_operator: + # The name of the service operator + name: "Example Foundation" + homepage: "https://example.com" + # An email address where users can contact the service operator. It is recommend to use a email list for this + mail_contact: "mytoken@example.com" + # A dedicated email address for privacy related topics can be given. If you do not have a dedicated mail for privacy, it can be omitted. + mail_privacy: "mytoken-privacy@example.com" + +# Configuration for the mytoken server +server: + # If TLS is not enabled, mytoken will listen on this port, default: 8000 + port: 80 + tls: + # Unless false TLS is enabled if 'cert' and 'key' are given + enabled: true + # Unless false port 80 is redirected to 443 + redirect_http: true + # The TLS certificate file + cert: + # The TLS certificate key file + key: + # If behind a load balancer or reverse proxy, set this option. Mytoken will read the client's ip address from this header. + proxy_header: "X-FORWARDED-FOR" + +# The database file for ip geo location. Will be installed by setup to this location. +geo_ip_db_file: "/root/mytoken/IP2LOCATION-LITE-DB1.IPV6.BIN" + +# Configuration of the mytoken API +api: + # Mytoken can support multiple api versions at the same time; this is the minimal version that is supported; older version won't be supported. Currently only 0 is supported. + min_supported_version: 0 + +# Configuration for the database +database: + hosts: + - "db_1" + - "db_2" + - "db_3" + user: "mytoken" + password: + # Read the db password from this file + password_file: "/run/secrets/db_password" + db: "mytoken" + # The interval (in seconds) in which mytoken tries to reconnect to db nodes that are down + try_reconnect_interval: 60 + +# Configuration for token signing +signing: + # The used algorithm + alg: "ES512" + # The file with the signing key + key_file: "/run/secrets/signing_key" + # If an RSA-based algorithm is used, this is the key len. Only needed when generating a new rsa key. + rsa_key_len: 2048 + +# Configuration for logging +logging: + # The web server access logs + access: + # The directory where the log file should be placed + dir: "/root/mytoken/logs" + # Indicates if mytoken should log additionally to stderr + stderr: true + # The mytoken internal logs + internal: + # The directory where the log file should be placed + dir: "/root/mytoken/logs" + # Indicates if mytoken should log additionally to stderr + stderr: true + # The minimal log level that should be logged + level: "error" + +# URL with documentation about the service +service_documentation: "https://mytoken-docs.data.kit.edu/" + +# Configuration and enabling/disabling for different features +features: + # The supported oidc flows + oidc_flows: + - "authorization_code" # Always enabled + + # Revocation for tokens issued by mytoken. Only disable this if you have good reasons for it. + token_revocation: + enabled: true + + # Endpoint to obtain different information about mytokens issued by this instance. + tokeninfo: + # Basic mytoken introspection (token-content useful when using short mytokens). Also gives information about validity and how often the token was used before. + # Must be enabled for usage with the web interface. + introspect: + enabled: true + # If enabled allows to query the tokeninfo endpoint to obtain the history of events for a given mytoken + event_history: + enabled: true + # If enabled allows to query the tokeninfo endpoint for a list (tree) of all subtokens for a given mytoken + subtoken_tree: + enabled: true + # If enabled allows a user to query the tokeninfo endpoint for a list of all its mytokens (the mytoken itself won't be returned) + list_mytokens: + enabled: true + + # Support for short mytokens + short_tokens: + enabled: true + len: 64 # Default 64, max 256 + + # Support for transfer codes for mytokens; transfer codes have the same len as polling codes and expire after the same time + transfer_codes: + enabled: true + + # Support for polling codes that are used by native applications. Only disable this if you have good reasons for it. + polling_codes: + enabled: true + len: 8 # Default 8, max 64 + expires_after: 300 # The time in seconds how long a polling code can be used + polling_interval: 5 # The interval in seconds the native application should wait between two polling attempts + + # Support for the access_token grant, i.e. a user can use an AT to obtain an ST. + access_token_grant: + enabled: true + + # Support for the private_key_jwt grant, i.e. a user can use an signed jwt to obtain an ST. + signed_jwt_grant: + enabled: true + + # Provides a web interface for in browser usage + web_interface: + enabled: true + +# The list of supported providers +providers: + - issuer: "https://example.provider.com/" + name: "Example provider" + client_id: "clientid" + client_secret: "clientsecret" + scopes: + - openid + - profile + # Maximum lifetime for mytokens for this issuer, given in seconds. On default the lifetime of mytokens is not restricted. + # Setting this value to 0, means that there is no maximum lifetime. + mytokens_max_lifetime: 0 diff --git a/docker/haproxy/example-haproxy.cfg b/docker/haproxy/example-haproxy.cfg new file mode 100644 index 00000000..5c44d0ca --- /dev/null +++ b/docker/haproxy/example-haproxy.cfg @@ -0,0 +1,57 @@ +global + log fd@2 local2 + user haproxy + group haproxy + master-worker + + ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 + ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 + ssl-default-bind-options prefer-client-ciphers no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets + + ssl-default-server-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384 + ssl-default-server-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256 + ssl-default-server-options no-sslv3 no-tlsv10 no-tlsv11 no-tls-tickets + +resolvers docker + nameserver dns1 127.0.0.11:53 + resolve_retries 3 + timeout resolve 1s + timeout retry 1s + hold other 10s + hold refused 10s + hold nx 10s + hold timeout 10s + hold valid 10s + hold obsolete 10s + +defaults + timeout connect 10s + timeout client 30s + timeout server 30s + log global + mode http + option httplog + +frontend fe_mytoken + mode http + bind :443 ssl crt /run/secrets/cert alpn h2,http/1.1 + bind :80 + redirect scheme https code 301 if !{ ssl_fc } + http-response set-header Strict-Transport-Security max-age=63072000 + use_backend stat if { path -i /admin/stats } + default_backend be_mytoken + +backend be_mytoken + balance roundrobin + option forwardfor + server mytoken_1 mytoken_1:80 check resolvers docker init-addr libc,none + server mytoken_2 mytoken_2:80 check resolvers docker init-addr libc,none + server mytoken_3 mytoken_3:80 check resolvers docker init-addr libc,none + +backend stat + stats enable + stats uri / + stats refresh 15s + stats show-legends + stats show-node + stats auth mytoken:mytoken diff --git a/go.mod b/go.mod index 0d9df9c8..23d86023 100644 --- a/go.mod +++ b/go.mod @@ -12,24 +12,20 @@ require ( github.com/gofiber/fiber/v2 v2.7.1 github.com/gofiber/helmet/v2 v2.1.0 github.com/gofiber/template v1.6.8 - github.com/golang/protobuf v1.4.3 // indirect github.com/ip2location/ip2location-go v8.3.0+incompatible github.com/jessevdk/go-flags v1.4.0 github.com/jinzhu/copier v0.1.0 github.com/jmoiron/sqlx v1.3.1 - github.com/klauspost/compress v1.11.12 // indirect - github.com/lestrrat-go/backoff/v2 v2.0.7 // indirect - github.com/lestrrat-go/httpcc v1.0.0 // indirect - github.com/lestrrat-go/iter v1.0.0 // indirect github.com/lestrrat-go/jwx v1.0.8 + github.com/oidc-mytoken/api v0.0.0-20210604072940-156be40bab89 + github.com/oidc-mytoken/lib v0.2.1-0.20210604073533-566d9af1210e github.com/satori/go.uuid v1.2.0 github.com/sirupsen/logrus v1.7.0 github.com/valyala/fasthttp v1.22.0 - github.com/valyala/tcplisten v1.0.0 // indirect + github.com/zachmann/cli/v2 v2.3.1-0.20210512144416-96dd678d93c7 golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83 + golang.org/x/mod v0.4.2 golang.org/x/oauth2 v0.0.0-20210125201302-af13f521f196 - golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d // indirect - golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf // indirect - google.golang.org/appengine v1.6.7 // indirect + golang.org/x/term v0.0.0-20210503060354-a79de5458b56 gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b ) diff --git a/go.sum b/go.sum index 3d307e87..d2148525 100644 --- a/go.sum +++ b/go.sum @@ -84,6 +84,8 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee 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/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -386,6 +388,11 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nishanths/exhaustive v0.0.0-20200525081945-8e46705b6132 h1:NjznefjSrral0MiR4KlB41io/d3OklvhcgQUdfZTqJE= github.com/nishanths/exhaustive v0.0.0-20200525081945-8e46705b6132/go.mod h1:wBEpHwM2OdmeNpdCvRPUlkEbBuaFmcK4Wv8Q7FuGW3c= +github.com/oidc-mytoken/api v0.0.0-20210604072940-156be40bab89 h1:sCjNUJwCUo+8mp/DTQxxUes4/Uchf76oS8+mDqzGQTU= +github.com/oidc-mytoken/api v0.0.0-20210604072940-156be40bab89/go.mod h1:S8t1XA42EFAgc3vUfis0g1LPGA4TXH0nfDynvgo6cwk= +github.com/oidc-mytoken/lib v0.2.1-0.20210604073533-566d9af1210e h1:UHswGI8MNr4dUh98rNOiNfNQHUK44va0d6wQwcFGa2c= +github.com/oidc-mytoken/lib v0.2.1-0.20210604073533-566d9af1210e/go.mod h1:vjpAk3Fn5Ow0MJMfy5lmLox29zQ89rhTNuvGjf/ql54= +github.com/oidc-mytoken/server v0.2.0/go.mod h1:6uFm+Za9NMK3gq4OOIeX3gs3T6leluVIWsGiM1zlQbA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.12.0 h1:Iw5WCbBcaAAd0fpRb1c9r5YCylv4XDoCSigm1zLevwU= @@ -424,6 +431,7 @@ github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:r github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.5.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryancurrah/gomodguard v1.1.0 h1:DWbye9KyMgytn8uYpuHkwf0RHqAYO6Ay/D0TbCpPtVU= github.com/ryancurrah/gomodguard v1.1.0/go.mod h1:4O8tr7hBODaGE6VIhfJDHcwzh5GUccKSJBU0UMXJFVM= @@ -441,6 +449,7 @@ github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e h1:MZM7FHLqUHYI0Y/mQAt github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 h1:llrF3Fs4018ePo4+G/HV/uQUqEI1HMDjCeOf2V6puPc= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= @@ -514,6 +523,8 @@ github.com/yosssi/ace v0.0.5/go.mod h1:ALfIzm2vT7t5ZE7uoIZqF3TQ7SAOyupFZnkrF5id+ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zachmann/cli/v2 v2.3.1-0.20210512144416-96dd678d93c7 h1:9Glvc2CvvmSJDS9SI0glTlEuNWyw0orKlnvjERE5Jl8= +github.com/zachmann/cli/v2 v2.3.1-0.20210512144416-96dd678d93c7/go.mod h1:QDdR6j5y3j9f94QTmu5+uimav/s4PiVVF0SU5Ec18Qc= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -563,8 +574,9 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -664,8 +676,9 @@ golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d h1:jbzgAvDZn8aEnytae+4ou0J0G golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf h1:MZ2shdL+ZM/XzY3ZGOnh4Nlpnxz5GSOhOmtHo3iPU6M= golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w= +golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -843,6 +856,7 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= diff --git a/internal/config/config.go b/internal/config/config.go index 8e54cac5..29464784 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -2,6 +2,7 @@ package config import ( "fmt" + "io/ioutil" "strings" "github.com/coreos/go-oidc/v3/oidc" @@ -152,15 +153,16 @@ type DBConf struct { Hosts []string `yaml:"hosts"` User string `yaml:"user"` Password string `yaml:"password"` + PasswordFile string `yaml:"password_file"` DB string `yaml:"db"` ReconnectInterval int64 `yaml:"try_reconnect_interval"` } type serverConf struct { - Hostname string `yaml:"hostname"` - Port int `yaml:"port"` - TLS tlsConf `yaml:"tls"` - Secure bool `yaml:"-"` // Secure indicates if the connection to the mytoken server is secure. This is independent of TLS, e.g. a Proxy can be used. + Port int `yaml:"port"` + TLS tlsConf `yaml:"tls"` + Secure bool `yaml:"-"` // Secure indicates if the connection to the mytoken server is secure. This is independent of TLS, e.g. a Proxy can be used. + ProxyHeader string `yaml:"proxy_header"` } type tlsConf struct { @@ -196,6 +198,23 @@ type ServiceOperatorConf struct { Privacy string `yaml:"mail_privacy"` } +// GetPassword returns the password for this database config. If necessary it reads it from the password file. +func (conf *DBConf) GetPassword() string { + if conf.Password != "" { + return conf.Password + } + if conf.PasswordFile == "" { + return "" + } + content, err := ioutil.ReadFile(conf.PasswordFile) + if err != nil { + log.WithError(err).Error() + return "" + } + conf.Password = strings.Split(string(content), "\n")[0] + return conf.Password +} + func (so *ServiceOperatorConf) validate() error { if so.Name == "" { return fmt.Errorf("invalid config: service_operator.name not set") @@ -226,9 +245,6 @@ func validate() error { if strings.HasPrefix(conf.IssuerURL, "http://") { conf.Server.Secure = false } - if conf.Server.Hostname == "" { - return fmt.Errorf("invalid config: server.hostname not set") - } if conf.Server.TLS.Enabled { if conf.Server.TLS.Key != "" && conf.Server.TLS.Cert != "" { conf.Server.Port = 443 diff --git a/internal/db/cluster/cluster.go b/internal/db/cluster/cluster.go index 2d8500c4..6444d849 100644 --- a/internal/db/cluster/cluster.go +++ b/internal/db/cluster/cluster.go @@ -15,20 +15,20 @@ import ( ) func NewFromConfig(conf config.DBConf) *Cluster { - c := New(len(conf.Hosts)) - c.AddNodes(conf) + c := newCluster(len(conf.Hosts)) c.conf = &conf + c.startReconnector() + c.AddNodes() log.Debug("Created db cluster") return c } -func New(size int) *Cluster { +func newCluster(size int) *Cluster { c := &Cluster{ active: make(chan *node, size), down: make(chan *node, size), stop: make(chan interface{}), } - c.startReconnector() return c } @@ -54,17 +54,17 @@ func (n *node) close() { } } -func (c *Cluster) AddNodes(conf config.DBConf) { - for _, host := range conf.Hosts { - if err := c.AddNode(conf, host); err != nil { +func (c *Cluster) AddNodes() { + for _, host := range c.conf.Hosts { + if err := c.AddNode(host); err != nil { log.WithError(err).Error() } } } -func (c *Cluster) AddNode(conf config.DBConf, host string) error { +func (c *Cluster) AddNode(host string) error { log.WithField("host", host).Debug("Adding node to db cluster") - dsn := fmt.Sprintf("%s:%s@%s(%s)/%s", conf.User, conf.Password, "tcp", host, conf.DB) + dsn := fmt.Sprintf("%s:%s@%s(%s)/%s", c.conf.User, c.conf.GetPassword(), "tcp", host, c.conf.DB) return c.addNode(&node{ dsn: dsn, }) diff --git a/internal/db/db.go b/internal/db/db.go index 112ba105..93f802fe 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -17,12 +17,17 @@ var db *cluster.Cluster // Connect connects to the database using the mytoken config func Connect() { + ConnectConfig(config.Get().DB) +} + +// ConnectConfig connects to the database using the passed config +func ConnectConfig(conf config.DBConf) { if db != nil { log.Debug("Closing existing db connections") db.Close() log.Debug("Done") } - db = cluster.NewFromConfig(config.Get().DB) + db = cluster.NewFromConfig(conf) } // NullString extends the sql.NullString diff --git a/internal/db/dbmigrate/migrate.go b/internal/db/dbmigrate/migrate.go new file mode 100644 index 00000000..dee6ad3e --- /dev/null +++ b/internal/db/dbmigrate/migrate.go @@ -0,0 +1,12 @@ +package dbmigrate + +type Commands struct { + Before []string `yaml:"before"` + After []string `yaml:"after"` +} + +type VersionCommands map[string]Commands + +var Migrate = VersionCommands{ + "0.2.0": {Before: v0_2_0_Before}, +} diff --git a/internal/db/dbdefinition/dbdefinition.go b/internal/db/dbmigrate/v0.2.0.go similarity index 85% rename from internal/db/dbdefinition/dbdefinition.go rename to internal/db/dbmigrate/v0.2.0.go index d2311828..3166e669 100644 --- a/internal/db/dbdefinition/dbdefinition.go +++ b/internal/db/dbmigrate/v0.2.0.go @@ -1,9 +1,7 @@ -package dbdefinition +package dbmigrate -// DDL holds all commands to create the necessary database tables -var DDL = []string{ - "" + - "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;", +var v0_2_0_Before = []string{ + "/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;", "/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;", "/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;", "/*!40101 SET NAMES utf8mb4 */;", @@ -13,15 +11,11 @@ var DDL = []string{ "/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;", "/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;", "/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;", - "" + - "--", + "-- Table structure for table `AT_Attributes`", - "--", - "" + - "DROP TABLE IF EXISTS `AT_Attributes`;", "/*!40101 SET @saved_cs_client = @@character_set_client */;", "/*!40101 SET character_set_client = utf8 */;", - "CREATE TABLE `AT_Attributes` (" + + "CREATE TABLE IF NOT EXISTS `AT_Attributes` (" + " `AT_id` bigint(20) unsigned NOT NULL," + " `attribute_id` int(10) unsigned NOT NULL," + " `attribute` text NOT NULL," + @@ -31,15 +25,11 @@ var DDL = []string{ " CONSTRAINT `AT_Attributes_FK_1` FOREIGN KEY (`attribute_id`) REFERENCES `Attributes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;", "/*!40101 SET character_set_client = @saved_cs_client */;", - "" + - "--", + "-- Table structure for table `AccessTokens`", - "--", - "" + - "DROP TABLE IF EXISTS `AccessTokens`;", "/*!40101 SET @saved_cs_client = @@character_set_client */;", "/*!40101 SET character_set_client = utf8 */;", - "CREATE TABLE `AccessTokens` (" + + "CREATE TABLE IF NOT EXISTS `AccessTokens` (" + " `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT," + " `token` text NOT NULL," + " `created` datetime NOT NULL DEFAULT current_timestamp()," + @@ -51,30 +41,22 @@ var DDL = []string{ " CONSTRAINT `AccessTokens_FK` FOREIGN KEY (`MT_id`) REFERENCES `MTokens` (`id`) ON DELETE CASCADE ON UPDATE CASCADE" + ") ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;", "/*!40101 SET character_set_client = @saved_cs_client */;", - "" + - "--", + "-- Table structure for table `Attributes`", - "--", - "" + - "DROP TABLE IF EXISTS `Attributes`;", "/*!40101 SET @saved_cs_client = @@character_set_client */;", "/*!40101 SET character_set_client = utf8 */;", - "CREATE TABLE `Attributes` (" + + "CREATE TABLE IF NOT EXISTS `Attributes` (" + " `id` int(10) unsigned NOT NULL AUTO_INCREMENT," + " `attribute` varchar(100) NOT NULL," + " PRIMARY KEY (`id`)," + " UNIQUE KEY `Attributes_UN` (`attribute`)" + ") ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;", "/*!40101 SET character_set_client = @saved_cs_client */;", - "" + - "--", + "-- Table structure for table `AuthInfo`", - "--", - "" + - "DROP TABLE IF EXISTS `AuthInfo`;", "/*!40101 SET @saved_cs_client = @@character_set_client */;", "/*!40101 SET character_set_client = utf8 */;", - "CREATE TABLE `AuthInfo` (" + + "CREATE TABLE IF NOT EXISTS `AuthInfo` (" + " `state_h` varchar(128) NOT NULL," + " `iss` text NOT NULL," + " `restrictions` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL CHECK (json_valid(`restrictions`))," + @@ -88,31 +70,22 @@ var DDL = []string{ " PRIMARY KEY (`state_h`)" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;", "/*!40101 SET character_set_client = @saved_cs_client */;", - "" + - "--", + "-- Table structure for table `EncryptionKeys`", - "--", - "" + - "DROP TABLE IF EXISTS `EncryptionKeys`;", "/*!40101 SET @saved_cs_client = @@character_set_client */;", "/*!40101 SET character_set_client = utf8 */;", - "CREATE TABLE `EncryptionKeys` (" + + "CREATE TABLE IF NOT EXISTS `EncryptionKeys` (" + " `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT," + " `encryption_key` text NOT NULL," + " `created` datetime NOT NULL DEFAULT current_timestamp()," + " PRIMARY KEY (`id`)" + ") ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;", "/*!40101 SET character_set_client = @saved_cs_client */;", - "" + - "--", + "-- Temporary table structure for view `EventHistory`", - "--", - "" + - "DROP TABLE IF EXISTS `EventHistory`;", - "/*!50001 DROP VIEW IF EXISTS `EventHistory`*/;", "SET @saved_cs_client = @@character_set_client;", "SET character_set_client = utf8;", - "/*!50001 CREATE TABLE `EventHistory` (" + + "/*!50001 CREATE TABLE IF NOT EXISTS `EventHistory` (" + " `time` tinyint NOT NULL," + " `MT_id` tinyint NOT NULL," + " `event` tinyint NOT NULL," + @@ -121,45 +94,33 @@ var DDL = []string{ " `user_agent` tinyint NOT NULL" + ") ENGINE=MyISAM */;", "SET character_set_client = @saved_cs_client;", - "" + - "--", + "-- Table structure for table `Events`", - "--", - "" + - "DROP TABLE IF EXISTS `Events`;", "/*!40101 SET @saved_cs_client = @@character_set_client */;", "/*!40101 SET character_set_client = utf8 */;", - "CREATE TABLE `Events` (" + + "CREATE TABLE IF NOT EXISTS `Events` (" + " `id` int(10) unsigned NOT NULL AUTO_INCREMENT," + " `event` varchar(100) NOT NULL," + " PRIMARY KEY (`id`)," + " UNIQUE KEY `Events_UN` (`event`)" + ") ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;", "/*!40101 SET character_set_client = @saved_cs_client */;", - "" + - "--", + "-- Table structure for table `Grants`", - "--", - "" + - "DROP TABLE IF EXISTS `Grants`;", "/*!40101 SET @saved_cs_client = @@character_set_client */;", "/*!40101 SET character_set_client = utf8 */;", - "CREATE TABLE `Grants` (" + + "CREATE TABLE IF NOT EXISTS `Grants` (" + " `id` int(10) unsigned NOT NULL AUTO_INCREMENT," + " `grant_type` varchar(100) NOT NULL," + " PRIMARY KEY (`id`)," + " UNIQUE KEY `Grants_UN` (`grant_type`)" + ") ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;", "/*!40101 SET character_set_client = @saved_cs_client */;", - "" + - "--", + "-- Table structure for table `MT_Events`", - "--", - "" + - "DROP TABLE IF EXISTS `MT_Events`;", "/*!40101 SET @saved_cs_client = @@character_set_client */;", "/*!40101 SET character_set_client = utf8 */;", - "CREATE TABLE `MT_Events` (" + + "CREATE TABLE IF NOT EXISTS `MT_Events` (" + " `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT," + " `MT_id` varchar(128) NOT NULL," + " `time` datetime NOT NULL DEFAULT current_timestamp()," + @@ -174,15 +135,11 @@ var DDL = []string{ " CONSTRAINT `MT_Events_FK_3` FOREIGN KEY (`event_id`) REFERENCES `Events` (`id`) ON DELETE CASCADE ON UPDATE CASCADE" + ") ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;", "/*!40101 SET character_set_client = @saved_cs_client */;", - "" + - "--", + "-- Table structure for table `MTokens`", - "--", - "" + - "DROP TABLE IF EXISTS `MTokens`;", "/*!40101 SET @saved_cs_client = @@character_set_client */;", "/*!40101 SET character_set_client = utf8 */;", - "CREATE TABLE `MTokens` (" + + "CREATE TABLE IF NOT EXISTS `MTokens` (" + " `id` varchar(128) NOT NULL," + " `parent_id` varchar(128) DEFAULT NULL," + " `root_id` varchar(128) DEFAULT NULL," + @@ -212,7 +169,7 @@ var DDL = []string{ "/*!50003 SET collation_connection = utf8mb4_general_ci */ ;", "/*!50003 SET @saved_sql_mode = @@sql_mode */ ;", "/*!50003 SET sql_mode = 'IGNORE_SPACE,STRICT_TRANS_TABLES,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION' */ ;", - "/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER updtrigger BEFORE UPDATE ON MTokens" + + "/*!50003 CREATE*/ /*!50017 DEFINER=`root`@`%`*/ /*!50003 TRIGGER IF NOT EXISTS updtrigger BEFORE UPDATE ON MTokens" + " FOR EACH ROW" + " BEGIN" + " IF NOT NEW.seqno <=> OLD.seqno THEN" + @@ -223,16 +180,11 @@ var DDL = []string{ "/*!50003 SET character_set_client = @saved_cs_client */ ;", "/*!50003 SET character_set_results = @saved_cs_results */ ;", "/*!50003 SET collation_connection = @saved_col_connection */ ;", - "" + - "--", + "-- Temporary table structure for view `MyTokens`", - "--", - "" + - "DROP TABLE IF EXISTS `MyTokens`;", - "/*!50001 DROP VIEW IF EXISTS `MyTokens`*/;", "SET @saved_cs_client = @@character_set_client;", "SET character_set_client = utf8;", - "/*!50001 CREATE TABLE `MyTokens` (" + + "/*!50001 CREATE TABLE IF NOT EXISTS `MyTokens` (" + " `id` tinyint NOT NULL," + " `seqno` tinyint NOT NULL," + " `parent_id` tinyint NOT NULL," + @@ -247,15 +199,11 @@ var DDL = []string{ " `encryption_key` tinyint NOT NULL" + ") ENGINE=MyISAM */;", "SET character_set_client = @saved_cs_client;", - "" + - "--", + "-- Table structure for table `ProxyTokens`", - "--", - "" + - "DROP TABLE IF EXISTS `ProxyTokens`;", "/*!40101 SET @saved_cs_client = @@character_set_client */;", "/*!40101 SET character_set_client = utf8 */;", - "CREATE TABLE `ProxyTokens` (" + + "CREATE TABLE IF NOT EXISTS `ProxyTokens` (" + " `id` varchar(128) NOT NULL," + " `jwt` text NOT NULL," + " `MT_id` varchar(128) DEFAULT NULL," + @@ -264,15 +212,11 @@ var DDL = []string{ " CONSTRAINT `ProxyTokens_FK` FOREIGN KEY (`MT_id`) REFERENCES `MTokens` (`id`) ON DELETE CASCADE ON UPDATE CASCADE" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;", "/*!40101 SET character_set_client = @saved_cs_client */;", - "" + - "--", + "-- Table structure for table `RT_EncryptionKeys`", - "--", - "" + - "DROP TABLE IF EXISTS `RT_EncryptionKeys`;", "/*!40101 SET @saved_cs_client = @@character_set_client */;", "/*!40101 SET character_set_client = utf8 */;", - "CREATE TABLE `RT_EncryptionKeys` (" + + "CREATE TABLE IF NOT EXISTS `RT_EncryptionKeys` (" + " `rt_id` bigint(20) unsigned NOT NULL," + " `MT_id` varchar(128) NOT NULL," + " `key_id` bigint(20) unsigned NOT NULL," + @@ -282,15 +226,11 @@ var DDL = []string{ " CONSTRAINT `RT_EncryptionKeys_FK_1` FOREIGN KEY (`rt_id`) REFERENCES `RefreshTokens` (`id`) ON DELETE CASCADE ON UPDATE CASCADE" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;", "/*!40101 SET character_set_client = @saved_cs_client */;", - "" + - "--", + "-- Table structure for table `RefreshTokens`", - "--", - "" + - "DROP TABLE IF EXISTS `RefreshTokens`;", "/*!40101 SET @saved_cs_client = @@character_set_client */;", "/*!40101 SET character_set_client = utf8 */;", - "CREATE TABLE `RefreshTokens` (" + + "CREATE TABLE IF NOT EXISTS `RefreshTokens` (" + " `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT," + " `rt` text NOT NULL," + " `created` datetime NOT NULL DEFAULT current_timestamp()," + @@ -298,15 +238,11 @@ var DDL = []string{ " PRIMARY KEY (`id`)" + ") ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;", "/*!40101 SET character_set_client = @saved_cs_client */;", - "" + - "--", + "-- Table structure for table `TokenUsages`", - "--", - "" + - "DROP TABLE IF EXISTS `TokenUsages`;", "/*!40101 SET @saved_cs_client = @@character_set_client */;", "/*!40101 SET character_set_client = utf8 */;", - "CREATE TABLE `TokenUsages` (" + + "CREATE TABLE IF NOT EXISTS `TokenUsages` (" + " `MT_id` varchar(128) NOT NULL," + " `restriction` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL CHECK (json_valid(`restriction`))," + " `usages_AT` int(10) unsigned NOT NULL DEFAULT 0," + @@ -316,16 +252,11 @@ var DDL = []string{ " CONSTRAINT `TokenUsages_FK` FOREIGN KEY (`MT_id`) REFERENCES `MTokens` (`id`) ON DELETE CASCADE ON UPDATE CASCADE" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;", "/*!40101 SET character_set_client = @saved_cs_client */;", - "" + - "--", + "-- Temporary table structure for view `TransferCodes`", - "--", - "" + - "DROP TABLE IF EXISTS `TransferCodes`;", - "/*!50001 DROP VIEW IF EXISTS `TransferCodes`*/;", "SET @saved_cs_client = @@character_set_client;", "SET character_set_client = utf8;", - "/*!50001 CREATE TABLE `TransferCodes` (" + + "/*!50001 CREATE TABLE IF NOT EXISTS `TransferCodes` (" + " `id` tinyint NOT NULL," + " `jwt` tinyint NOT NULL," + " `created` tinyint NOT NULL," + @@ -336,15 +267,11 @@ var DDL = []string{ " `consent_declined` tinyint NOT NULL" + ") ENGINE=MyISAM */;", "SET character_set_client = @saved_cs_client;", - "" + - "--", + "-- Table structure for table `TransferCodesAttributes`", - "--", - "" + - "DROP TABLE IF EXISTS `TransferCodesAttributes`;", "/*!40101 SET @saved_cs_client = @@character_set_client */;", "/*!40101 SET character_set_client = utf8 */;", - "CREATE TABLE `TransferCodesAttributes` (" + + "CREATE TABLE IF NOT EXISTS `TransferCodesAttributes` (" + " `id` varchar(128) NOT NULL," + " `created` datetime NOT NULL DEFAULT current_timestamp()," + " `expires_in` int(11) NOT NULL," + @@ -356,15 +283,11 @@ var DDL = []string{ " CONSTRAINT `TransferCodesAttributes_FK` FOREIGN KEY (`id`) REFERENCES `ProxyTokens` (`id`) ON DELETE CASCADE ON UPDATE CASCADE" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;", "/*!40101 SET character_set_client = @saved_cs_client */;", - "" + - "--", + "-- Table structure for table `UserGrant_Attributes`", - "--", - "" + - "DROP TABLE IF EXISTS `UserGrant_Attributes`;", "/*!40101 SET @saved_cs_client = @@character_set_client */;", "/*!40101 SET character_set_client = utf8 */;", - "CREATE TABLE `UserGrant_Attributes` (" + + "CREATE TABLE IF NOT EXISTS `UserGrant_Attributes` (" + " `user_id` bigint(20) unsigned NOT NULL," + " `grant_id` int(10) unsigned NOT NULL," + " `attribute_id` int(10) unsigned NOT NULL," + @@ -377,15 +300,11 @@ var DDL = []string{ " CONSTRAINT `UserGrant_Attributes_FK_3` FOREIGN KEY (`attribute_id`) REFERENCES `Attributes` (`id`) ON DELETE CASCADE ON UPDATE CASCADE" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;", "/*!40101 SET character_set_client = @saved_cs_client */;", - "" + - "--", + "-- Table structure for table `UserGrants`", - "--", - "" + - "DROP TABLE IF EXISTS `UserGrants`;", "/*!40101 SET @saved_cs_client = @@character_set_client */;", "/*!40101 SET character_set_client = utf8 */;", - "CREATE TABLE `UserGrants` (" + + "CREATE TABLE IF NOT EXISTS `UserGrants` (" + " `user_id` bigint(20) unsigned NOT NULL," + " `grant_id` int(10) unsigned NOT NULL," + " `enabled` bit(1) NOT NULL," + @@ -395,15 +314,11 @@ var DDL = []string{ " CONSTRAINT `UserGrants_FK_1` FOREIGN KEY (`user_id`) REFERENCES `Users` (`id`) ON DELETE CASCADE ON UPDATE CASCADE" + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;", "/*!40101 SET character_set_client = @saved_cs_client */;", - "" + - "--", + "-- Table structure for table `Users`", - "--", - "" + - "DROP TABLE IF EXISTS `Users`;", "/*!40101 SET @saved_cs_client = @@character_set_client */;", "/*!40101 SET character_set_client = utf8 */;", - "CREATE TABLE `Users` (" + + "CREATE TABLE IF NOT EXISTS `Users` (" + " `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT," + " `sub` varchar(512) NOT NULL," + " `iss` varchar(256) NOT NULL," + @@ -413,12 +328,17 @@ var DDL = []string{ " UNIQUE KEY `Users_UN` (`sub`,`iss`)" + ") ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;", "/*!40101 SET character_set_client = @saved_cs_client */;", - "" + - "--", + + "-- Table structure for table `version`", + "CREATE TABLE IF NOT EXISTS `version` (" + + "`version` varchar(64) NOT NULL," + + "`bef` datetime DEFAULT NULL," + + "`aft` datetime DEFAULT NULL," + + "PRIMARY KEY (`version`)" + + ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4", + "-- Final view structure for view `EventHistory`", - "--", - "" + - "/*!50001 DROP TABLE IF EXISTS `EventHistory`*/;", + "/*!50001 DROP TABLE IF EXISTS `EventHistory`*/;", "/*!50001 DROP VIEW IF EXISTS `EventHistory`*/;", "/*!50001 SET @saved_cs_client = @@character_set_client */;", "/*!50001 SET @saved_cs_results = @@character_set_results */;", @@ -431,12 +351,9 @@ var DDL = []string{ "/*!50001 SET character_set_client = @saved_cs_client */;", "/*!50001 SET character_set_results = @saved_cs_results */;", "/*!50001 SET collation_connection = @saved_col_connection */;", - "" + - "--", + "-- Final view structure for view `MyTokens`", - "--", - "" + - "/*!50001 DROP TABLE IF EXISTS `MyTokens`*/;", + "/*!50001 DROP TABLE IF EXISTS `MyTokens`*/;", "/*!50001 DROP VIEW IF EXISTS `MyTokens`*/;", "/*!50001 SET @saved_cs_client = @@character_set_client */;", "/*!50001 SET @saved_cs_results = @@character_set_results */;", @@ -449,12 +366,9 @@ var DDL = []string{ "/*!50001 SET character_set_client = @saved_cs_client */;", "/*!50001 SET character_set_results = @saved_cs_results */;", "/*!50001 SET collation_connection = @saved_col_connection */;", - "" + - "--", + "-- Final view structure for view `TransferCodes`", - "--", - "" + - "/*!50001 DROP TABLE IF EXISTS `TransferCodes`*/;", + "/*!50001 DROP TABLE IF EXISTS `TransferCodes`*/;", "/*!50001 DROP VIEW IF EXISTS `TransferCodes`*/;", "/*!50001 SET @saved_cs_client = @@character_set_client */;", "/*!50001 SET @saved_cs_results = @@character_set_results */;", @@ -468,12 +382,43 @@ var DDL = []string{ "/*!50001 SET character_set_results = @saved_cs_results */;", "/*!50001 SET collation_connection = @saved_col_connection */;", "/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;", - "" + - "/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;", + "/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;", "/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;", "/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;", "/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;", "/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;", "/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;", "/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;", + + // Predefined Values + "INSERT IGNORE INTO Attributes(attribute) VALUES('scope')", + "INSERT IGNORE INTO Attributes(attribute) VALUES('audience')", + "INSERT IGNORE INTO Attributes(attribute) VALUES('capability')", + + "INSERT IGNORE INTO Events (event) VALUES('unknown')", + "INSERT IGNORE INTO Events (event) VALUES('created')", + "INSERT IGNORE INTO Events (event) VALUES('AT_created')", + "INSERT IGNORE INTO Events (event) VALUES('MT_created')", + "INSERT IGNORE INTO Events (event) VALUES('tokeninfo_introspect')", + "INSERT IGNORE INTO Events (event) VALUES('tokeninfo_history')", + "INSERT IGNORE INTO Events (event) VALUES('tokeninfo_tree')", + "INSERT IGNORE INTO Events (event) VALUES('tokeninfo_list_mytokens')", + "INSERT IGNORE INTO Events (event) VALUES('mng_enabled_AT_grant')", + "INSERT IGNORE INTO Events (event) VALUES('mng_disabled_AT_grant')", + "INSERT IGNORE INTO Events (event) VALUES('mng_enabled_JWT_grant')", + "INSERT IGNORE INTO Events (event) VALUES('mng_disabled_JWT_grant')", + "INSERT IGNORE INTO Events (event) VALUES('mng_linked_grant')", + "INSERT IGNORE INTO Events (event) VALUES('mng_unlinked_grant')", + "INSERT IGNORE INTO Events (event) VALUES('mng_enabled_tracing')", + "INSERT IGNORE INTO Events (event) VALUES('mng_disabled_tracing')", + "INSERT IGNORE INTO Events (event) VALUES('inherited_RT')", + "INSERT IGNORE INTO Events (event) VALUES('transfer_code_created')", + "INSERT IGNORE INTO Events (event) VALUES('transfer_code_used')", + + "INSERT IGNORE INTO Grants (grant_type) VALUES('mytoken')", + "INSERT IGNORE INTO Grants (grant_type) VALUES('oidc_flow')", + "INSERT IGNORE INTO Grants (grant_type) VALUES('polling_code')", + "INSERT IGNORE INTO Grants (grant_type) VALUES('access_token')", + "INSERT IGNORE INTO Grants (grant_type) VALUES('private_key_jwt')", + "INSERT IGNORE INTO Grants (grant_type) VALUES('transfer_code')", } diff --git a/internal/db/dbrepo/authcodeinforepo/authcodeInfo.go b/internal/db/dbrepo/authcodeinforepo/authcodeInfo.go index 68a4734e..87e95ca2 100644 --- a/internal/db/dbrepo/authcodeinforepo/authcodeInfo.go +++ b/internal/db/dbrepo/authcodeinforepo/authcodeInfo.go @@ -4,11 +4,11 @@ import ( "github.com/jmoiron/sqlx" log "github.com/sirupsen/logrus" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/config" "github.com/oidc-mytoken/server/internal/db" "github.com/oidc-mytoken/server/internal/db/dbrepo/authcodeinforepo/state" "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/transfercoderepo" - "github.com/oidc-mytoken/server/pkg/api/v0" "github.com/oidc-mytoken/server/shared/mytoken/restrictions" ) diff --git a/internal/db/dbrepo/eventrepo/event.go b/internal/db/dbrepo/eventrepo/event.go index fdf62cbe..700edc3c 100644 --- a/internal/db/dbrepo/eventrepo/event.go +++ b/internal/db/dbrepo/eventrepo/event.go @@ -3,8 +3,8 @@ package eventrepo import ( "github.com/jmoiron/sqlx" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/db" - "github.com/oidc-mytoken/server/pkg/api/v0" event "github.com/oidc-mytoken/server/shared/mytoken/event/pkg" "github.com/oidc-mytoken/server/shared/mytoken/pkg/mtid" ) diff --git a/internal/db/dbrepo/eventrepo/eventHistory.go b/internal/db/dbrepo/eventrepo/eventHistory.go index c980e86a..155819e6 100644 --- a/internal/db/dbrepo/eventrepo/eventHistory.go +++ b/internal/db/dbrepo/eventrepo/eventHistory.go @@ -3,8 +3,8 @@ package eventrepo import ( "github.com/jmoiron/sqlx" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/db" - "github.com/oidc-mytoken/server/pkg/api/v0" "github.com/oidc-mytoken/server/shared/mytoken/pkg/mtid" "github.com/oidc-mytoken/server/shared/utils/unixtime" ) diff --git a/internal/db/dbrepo/mytokenrepo/mytoken.go b/internal/db/dbrepo/mytokenrepo/mytoken.go index faf4fcf7..098eb81f 100644 --- a/internal/db/dbrepo/mytokenrepo/mytoken.go +++ b/internal/db/dbrepo/mytokenrepo/mytoken.go @@ -8,8 +8,8 @@ import ( "github.com/jmoiron/sqlx" log "github.com/sirupsen/logrus" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/db" - "github.com/oidc-mytoken/server/pkg/api/v0" eventService "github.com/oidc-mytoken/server/shared/mytoken/event" event "github.com/oidc-mytoken/server/shared/mytoken/event/pkg" mytoken "github.com/oidc-mytoken/server/shared/mytoken/pkg" diff --git a/internal/db/dbrepo/mytokenrepo/transfercoderepo/pollingCodeTmpST.go b/internal/db/dbrepo/mytokenrepo/transfercoderepo/pollingCodeTmpST.go index 5135141f..9d216df8 100644 --- a/internal/db/dbrepo/mytokenrepo/transfercoderepo/pollingCodeTmpST.go +++ b/internal/db/dbrepo/mytokenrepo/transfercoderepo/pollingCodeTmpST.go @@ -6,9 +6,9 @@ import ( "github.com/jmoiron/sqlx" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/db" "github.com/oidc-mytoken/server/internal/db/dbrepo/authcodeinforepo/state" - "github.com/oidc-mytoken/server/pkg/api/v0" "github.com/oidc-mytoken/server/shared/model" eventService "github.com/oidc-mytoken/server/shared/mytoken/event" event "github.com/oidc-mytoken/server/shared/mytoken/event/pkg" diff --git a/internal/db/dbrepo/mytokenrepo/tree/tree.go b/internal/db/dbrepo/mytokenrepo/tree/tree.go index dcb0bc38..8f637470 100644 --- a/internal/db/dbrepo/mytokenrepo/tree/tree.go +++ b/internal/db/dbrepo/mytokenrepo/tree/tree.go @@ -3,8 +3,8 @@ package tree import ( "github.com/jmoiron/sqlx" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/db" - "github.com/oidc-mytoken/server/pkg/api/v0" "github.com/oidc-mytoken/server/shared/mytoken/pkg/mtid" "github.com/oidc-mytoken/server/shared/utils/unixtime" ) diff --git a/internal/db/dbrepo/versionrepo/versionrepo.go b/internal/db/dbrepo/versionrepo/versionrepo.go new file mode 100644 index 00000000..98b03a05 --- /dev/null +++ b/internal/db/dbrepo/versionrepo/versionrepo.go @@ -0,0 +1,98 @@ +package versionrepo + +import ( + "sort" + "strings" + + "github.com/go-sql-driver/mysql" + "github.com/jmoiron/sqlx" + "github.com/oidc-mytoken/server/internal/db" + "github.com/oidc-mytoken/server/internal/db/dbmigrate" + "github.com/oidc-mytoken/server/internal/model/version" + log "github.com/sirupsen/logrus" + "golang.org/x/mod/semver" +) + +func SetVersionBefore(tx *sqlx.Tx, version string) error { + return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { + _, err := tx.Exec(`INSERT INTO version (version, bef) VALUES(?, current_timestamp()) ON DUPLICATE KEY UPDATE bef=current_timestamp()`, version) + return err + }) +} + +func SetVersionAfter(tx *sqlx.Tx, version string) error { + return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { + _, err := tx.Exec(`INSERT INTO version (version, aft) VALUES(?, current_timestamp()) ON DUPLICATE KEY UPDATE aft=current_timestamp()`, version) + return err + }) +} + +type UpdateTimes struct { + Version string + Before mysql.NullTime `db:"bef"` + After mysql.NullTime `db:"aft"` +} + +type DBVersionState []UpdateTimes + +func (state DBVersionState) Len() int { return len(state) } +func (state DBVersionState) Swap(i, j int) { state[i], state[j] = state[j], state[i] } +func (state DBVersionState) Less(i, j int) bool { + a, b := state[i].Version, state[j].Version + return semver.Compare(a, b) < 0 +} + +func (state DBVersionState) Sort() { + sort.Sort(state) +} + +// dbHasAllVersions checks that the database is compatible with the current version; assumes that DBVersionState is ordered +func (state DBVersionState) dBHasAllVersions() bool { + for v, cmds := range dbmigrate.Migrate { + if !state.dBHasVersion(v, cmds) { + return false + } + } + return true +} + +// dbHasVersion checks that the database is compatible with the passed version; assumes that DBVersionState is ordered +func (state DBVersionState) dBHasVersion(v string, cmds dbmigrate.Commands) bool { + i := sort.Search(len(state), func(i int) bool { + return semver.Compare(state[i].Version, v) >= 0 + }) + if i >= len(state) || state[i].Version != v { // we have to check that i really points to v + return false + } + ok := true + if len(cmds.Before) > 0 { + ok = ok && state[i].Before.Valid + } + if len(cmds.After) > 0 { + ok = ok && state[i].After.Valid + } + return ok +} + +func GetVersionState(tx *sqlx.Tx) (state DBVersionState, err error) { + err = db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { + return tx.Select(&state, `SELECT version, bef, aft FROM version`) + }) + if err != nil && strings.HasPrefix(err.Error(), "Error 1146: Table") { // Ignore table does not exist error + err = nil + } + state.Sort() + return +} + +// ConnectToVersion connects to the mytoken database and asserts that the database is up-to-date to the current version +func ConnectToVersion() { + db.Connect() + state, err := GetVersionState(nil) + if err != nil { + log.WithError(err).Fatal() + } + if !state.dBHasAllVersions() { + log.WithField("version", version.VERSION()).Fatal("database schema not updated to this server version") + } +} diff --git a/internal/endpoints/configuration/configurationEndpoint.go b/internal/endpoints/configuration/configurationEndpoint.go index 45307eb9..aa5d04c7 100644 --- a/internal/endpoints/configuration/configurationEndpoint.go +++ b/internal/endpoints/configuration/configurationEndpoint.go @@ -3,12 +3,12 @@ package configuration import ( "github.com/gofiber/fiber/v2" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/config" "github.com/oidc-mytoken/server/internal/endpoints/configuration/pkg" "github.com/oidc-mytoken/server/internal/model" "github.com/oidc-mytoken/server/internal/model/version" "github.com/oidc-mytoken/server/internal/server/routes" - "github.com/oidc-mytoken/server/pkg/api/v0" pkgModel "github.com/oidc-mytoken/server/shared/model" "github.com/oidc-mytoken/server/shared/utils" ) diff --git a/internal/endpoints/configuration/pkg/mytokenConfiguration.go b/internal/endpoints/configuration/pkg/mytokenConfiguration.go index 846c88ab..1e8dc536 100644 --- a/internal/endpoints/configuration/pkg/mytokenConfiguration.go +++ b/internal/endpoints/configuration/pkg/mytokenConfiguration.go @@ -1,7 +1,7 @@ package pkg import ( - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/shared/model" ) diff --git a/internal/endpoints/consent/consent.go b/internal/endpoints/consent/consent.go index e5e00a0d..e4d98725 100644 --- a/internal/endpoints/consent/consent.go +++ b/internal/endpoints/consent/consent.go @@ -9,6 +9,7 @@ import ( "github.com/gofiber/fiber/v2" log "github.com/sirupsen/logrus" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/config" "github.com/oidc-mytoken/server/internal/db/dbrepo/authcodeinforepo" "github.com/oidc-mytoken/server/internal/db/dbrepo/authcodeinforepo/state" @@ -16,7 +17,6 @@ import ( "github.com/oidc-mytoken/server/internal/endpoints/consent/pkg" "github.com/oidc-mytoken/server/internal/model" "github.com/oidc-mytoken/server/internal/oidc/authcode" - "github.com/oidc-mytoken/server/pkg/api/v0" model2 "github.com/oidc-mytoken/server/shared/model" "github.com/oidc-mytoken/server/shared/utils" ) diff --git a/internal/endpoints/consent/pkg/capability.go b/internal/endpoints/consent/pkg/capability.go index 1b74bd95..39b01221 100644 --- a/internal/endpoints/consent/pkg/capability.go +++ b/internal/endpoints/consent/pkg/capability.go @@ -1,7 +1,7 @@ package pkg import ( - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/shared/utils" ) diff --git a/internal/endpoints/consent/pkg/consentrequest.go b/internal/endpoints/consent/pkg/consentrequest.go index 68826200..7f60e720 100644 --- a/internal/endpoints/consent/pkg/consentrequest.go +++ b/internal/endpoints/consent/pkg/consentrequest.go @@ -1,7 +1,7 @@ package pkg import ( - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/shared/mytoken/restrictions" ) diff --git a/internal/endpoints/revocation/revocationEndpoint.go b/internal/endpoints/revocation/revocationEndpoint.go index 332f2e97..25191bb8 100644 --- a/internal/endpoints/revocation/revocationEndpoint.go +++ b/internal/endpoints/revocation/revocationEndpoint.go @@ -9,11 +9,11 @@ import ( "github.com/jmoiron/sqlx" log "github.com/sirupsen/logrus" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/config" "github.com/oidc-mytoken/server/internal/db" "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/transfercoderepo" "github.com/oidc-mytoken/server/internal/model" - "github.com/oidc-mytoken/server/pkg/api/v0" sharedModel "github.com/oidc-mytoken/server/shared/model" "github.com/oidc-mytoken/server/shared/mytoken" mytokenPkg "github.com/oidc-mytoken/server/shared/mytoken/pkg" diff --git a/internal/endpoints/token/access/accessTokenEndpoint.go b/internal/endpoints/token/access/accessTokenEndpoint.go index 802c4bfa..4844483f 100644 --- a/internal/endpoints/token/access/accessTokenEndpoint.go +++ b/internal/endpoints/token/access/accessTokenEndpoint.go @@ -7,6 +7,7 @@ import ( "github.com/jmoiron/sqlx" log "github.com/sirupsen/logrus" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/config" "github.com/oidc-mytoken/server/internal/db" "github.com/oidc-mytoken/server/internal/db/dbrepo/accesstokenrepo" @@ -16,7 +17,6 @@ import ( serverModel "github.com/oidc-mytoken/server/internal/model" "github.com/oidc-mytoken/server/internal/oidc/refresh" "github.com/oidc-mytoken/server/internal/utils/ctxUtils" - "github.com/oidc-mytoken/server/pkg/api/v0" "github.com/oidc-mytoken/server/shared/model" eventService "github.com/oidc-mytoken/server/shared/mytoken/event" event "github.com/oidc-mytoken/server/shared/mytoken/event/pkg" diff --git a/internal/endpoints/token/access/pkg/accessTokenRequest.go b/internal/endpoints/token/access/pkg/accessTokenRequest.go index 8a2ecf99..ab7b707a 100644 --- a/internal/endpoints/token/access/pkg/accessTokenRequest.go +++ b/internal/endpoints/token/access/pkg/accessTokenRequest.go @@ -1,7 +1,7 @@ package pkg import ( - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/shared/model" "github.com/oidc-mytoken/server/shared/mytoken/token" ) diff --git a/internal/endpoints/token/mytoken/mytokenEndpoint.go b/internal/endpoints/token/mytoken/mytokenEndpoint.go index 417a1a29..0bd8525d 100644 --- a/internal/endpoints/token/mytoken/mytokenEndpoint.go +++ b/internal/endpoints/token/mytoken/mytokenEndpoint.go @@ -6,13 +6,13 @@ import ( "github.com/gofiber/fiber/v2" log "github.com/sirupsen/logrus" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/config" response "github.com/oidc-mytoken/server/internal/endpoints/token/mytoken/pkg" "github.com/oidc-mytoken/server/internal/endpoints/token/mytoken/polling" serverModel "github.com/oidc-mytoken/server/internal/model" "github.com/oidc-mytoken/server/internal/oidc/authcode" "github.com/oidc-mytoken/server/internal/utils/ctxUtils" - "github.com/oidc-mytoken/server/pkg/api/v0" "github.com/oidc-mytoken/server/shared/model" "github.com/oidc-mytoken/server/shared/mytoken" ) diff --git a/internal/endpoints/token/mytoken/pkg/myTokenRequest.go b/internal/endpoints/token/mytoken/pkg/myTokenRequest.go index 54607042..ee37561e 100644 --- a/internal/endpoints/token/mytoken/pkg/myTokenRequest.go +++ b/internal/endpoints/token/mytoken/pkg/myTokenRequest.go @@ -3,7 +3,7 @@ package pkg import ( "encoding/json" - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/shared/model" "github.com/oidc-mytoken/server/shared/mytoken/restrictions" "github.com/oidc-mytoken/server/shared/mytoken/token" diff --git a/internal/endpoints/token/mytoken/pkg/myTokenResponse.go b/internal/endpoints/token/mytoken/pkg/myTokenResponse.go index 9a1d8f04..f01b6a83 100644 --- a/internal/endpoints/token/mytoken/pkg/myTokenResponse.go +++ b/internal/endpoints/token/mytoken/pkg/myTokenResponse.go @@ -1,7 +1,7 @@ package pkg import ( - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/shared/model" "github.com/oidc-mytoken/server/shared/mytoken/restrictions" ) diff --git a/internal/endpoints/token/mytoken/pkg/oidcFlowRequest.go b/internal/endpoints/token/mytoken/pkg/oidcFlowRequest.go index 9d0b6580..21f0b932 100644 --- a/internal/endpoints/token/mytoken/pkg/oidcFlowRequest.go +++ b/internal/endpoints/token/mytoken/pkg/oidcFlowRequest.go @@ -5,7 +5,7 @@ import ( "encoding/json" "fmt" - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/shared/model" "github.com/oidc-mytoken/server/shared/mytoken/restrictions" ) diff --git a/internal/endpoints/token/mytoken/pkg/pollingCodeRequest.go b/internal/endpoints/token/mytoken/pkg/pollingCodeRequest.go index e219b8e5..95e670b0 100644 --- a/internal/endpoints/token/mytoken/pkg/pollingCodeRequest.go +++ b/internal/endpoints/token/mytoken/pkg/pollingCodeRequest.go @@ -1,7 +1,7 @@ package pkg import ( - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/shared/model" ) diff --git a/internal/endpoints/token/mytoken/pkg/transferCodeRequest.go b/internal/endpoints/token/mytoken/pkg/transferCodeRequest.go index 7f45392b..351c13e4 100644 --- a/internal/endpoints/token/mytoken/pkg/transferCodeRequest.go +++ b/internal/endpoints/token/mytoken/pkg/transferCodeRequest.go @@ -1,7 +1,7 @@ package pkg import ( - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/shared/model" ) diff --git a/internal/endpoints/token/mytoken/pkg/transferCodeResponse.go b/internal/endpoints/token/mytoken/pkg/transferCodeResponse.go index 466265c2..17cfdb8c 100644 --- a/internal/endpoints/token/mytoken/pkg/transferCodeResponse.go +++ b/internal/endpoints/token/mytoken/pkg/transferCodeResponse.go @@ -1,7 +1,7 @@ package pkg import ( - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/shared/model" ) diff --git a/internal/endpoints/token/mytoken/polling/pollingEndpoint.go b/internal/endpoints/token/mytoken/polling/pollingEndpoint.go index 9d14ca50..37582eaf 100644 --- a/internal/endpoints/token/mytoken/polling/pollingEndpoint.go +++ b/internal/endpoints/token/mytoken/polling/pollingEndpoint.go @@ -6,11 +6,11 @@ import ( "github.com/gofiber/fiber/v2" log "github.com/sirupsen/logrus" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/transfercoderepo" response "github.com/oidc-mytoken/server/internal/endpoints/token/mytoken/pkg" "github.com/oidc-mytoken/server/internal/model" "github.com/oidc-mytoken/server/internal/utils/ctxUtils" - "github.com/oidc-mytoken/server/pkg/api/v0" mytoken "github.com/oidc-mytoken/server/shared/mytoken/pkg" ) diff --git a/internal/endpoints/token/mytoken/transferEndpoint.go b/internal/endpoints/token/mytoken/transferEndpoint.go index 6ab79b16..f87f6f36 100644 --- a/internal/endpoints/token/mytoken/transferEndpoint.go +++ b/internal/endpoints/token/mytoken/transferEndpoint.go @@ -5,12 +5,12 @@ import ( "github.com/gofiber/fiber/v2" + "github.com/oidc-mytoken/api/v0" dbhelper "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/mytokenrepohelper" "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/transfercoderepo" "github.com/oidc-mytoken/server/internal/endpoints/token/mytoken/pkg" "github.com/oidc-mytoken/server/internal/model" "github.com/oidc-mytoken/server/internal/utils/ctxUtils" - "github.com/oidc-mytoken/server/pkg/api/v0" pkgModel "github.com/oidc-mytoken/server/shared/model" mytoken "github.com/oidc-mytoken/server/shared/mytoken/pkg" "github.com/oidc-mytoken/server/shared/utils" diff --git a/internal/endpoints/tokeninfo/history.go b/internal/endpoints/tokeninfo/history.go index d6d791bf..f56f2a29 100644 --- a/internal/endpoints/tokeninfo/history.go +++ b/internal/endpoints/tokeninfo/history.go @@ -7,11 +7,11 @@ import ( "github.com/gofiber/fiber/v2" "github.com/jmoiron/sqlx" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/db" "github.com/oidc-mytoken/server/internal/db/dbrepo/eventrepo" "github.com/oidc-mytoken/server/internal/endpoints/tokeninfo/pkg" "github.com/oidc-mytoken/server/internal/model" - "github.com/oidc-mytoken/server/pkg/api/v0" eventService "github.com/oidc-mytoken/server/shared/mytoken/event" event "github.com/oidc-mytoken/server/shared/mytoken/event/pkg" mytoken "github.com/oidc-mytoken/server/shared/mytoken/pkg" diff --git a/internal/endpoints/tokeninfo/introspect.go b/internal/endpoints/tokeninfo/introspect.go index f5cdd11d..ac26bec8 100644 --- a/internal/endpoints/tokeninfo/introspect.go +++ b/internal/endpoints/tokeninfo/introspect.go @@ -4,10 +4,10 @@ import ( "github.com/gofiber/fiber/v2" "github.com/jmoiron/sqlx" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/db" "github.com/oidc-mytoken/server/internal/endpoints/tokeninfo/pkg" "github.com/oidc-mytoken/server/internal/model" - "github.com/oidc-mytoken/server/pkg/api/v0" eventService "github.com/oidc-mytoken/server/shared/mytoken/event" event "github.com/oidc-mytoken/server/shared/mytoken/event/pkg" mytoken "github.com/oidc-mytoken/server/shared/mytoken/pkg" diff --git a/internal/endpoints/tokeninfo/list.go b/internal/endpoints/tokeninfo/list.go index 237b28ef..6a4c48b9 100644 --- a/internal/endpoints/tokeninfo/list.go +++ b/internal/endpoints/tokeninfo/list.go @@ -7,11 +7,11 @@ import ( "github.com/gofiber/fiber/v2" "github.com/jmoiron/sqlx" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/db" "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/tree" "github.com/oidc-mytoken/server/internal/endpoints/tokeninfo/pkg" "github.com/oidc-mytoken/server/internal/model" - "github.com/oidc-mytoken/server/pkg/api/v0" eventService "github.com/oidc-mytoken/server/shared/mytoken/event" event "github.com/oidc-mytoken/server/shared/mytoken/event/pkg" mytoken "github.com/oidc-mytoken/server/shared/mytoken/pkg" diff --git a/internal/endpoints/tokeninfo/pkg/tokenIntrospectResponse.go b/internal/endpoints/tokeninfo/pkg/tokenIntrospectResponse.go index 0f11a523..56197022 100644 --- a/internal/endpoints/tokeninfo/pkg/tokenIntrospectResponse.go +++ b/internal/endpoints/tokeninfo/pkg/tokenIntrospectResponse.go @@ -1,7 +1,7 @@ package pkg import ( - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" mytoken "github.com/oidc-mytoken/server/shared/mytoken/pkg" ) diff --git a/internal/endpoints/tokeninfo/pkg/tokeninfoRequest.go b/internal/endpoints/tokeninfo/pkg/tokeninfoRequest.go index a5d22afe..8bb4603f 100644 --- a/internal/endpoints/tokeninfo/pkg/tokeninfoRequest.go +++ b/internal/endpoints/tokeninfo/pkg/tokeninfoRequest.go @@ -1,7 +1,7 @@ package pkg import ( - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/shared/model" "github.com/oidc-mytoken/server/shared/mytoken/token" ) diff --git a/internal/endpoints/tokeninfo/tree.go b/internal/endpoints/tokeninfo/tree.go index 593d5fda..1556dca9 100644 --- a/internal/endpoints/tokeninfo/tree.go +++ b/internal/endpoints/tokeninfo/tree.go @@ -7,11 +7,11 @@ import ( "github.com/gofiber/fiber/v2" "github.com/jmoiron/sqlx" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/db" "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/tree" "github.com/oidc-mytoken/server/internal/endpoints/tokeninfo/pkg" "github.com/oidc-mytoken/server/internal/model" - "github.com/oidc-mytoken/server/pkg/api/v0" eventService "github.com/oidc-mytoken/server/shared/mytoken/event" event "github.com/oidc-mytoken/server/shared/mytoken/event/pkg" mytoken "github.com/oidc-mytoken/server/shared/mytoken/pkg" diff --git a/internal/model/response.go b/internal/model/response.go index efba7a9d..56faee62 100644 --- a/internal/model/response.go +++ b/internal/model/response.go @@ -4,7 +4,7 @@ import ( "github.com/gofiber/fiber/v2" "github.com/valyala/fasthttp" - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/shared/model" ) diff --git a/internal/oidc/authcode/authcode.go b/internal/oidc/authcode/authcode.go index 88b195c4..3d978d1a 100644 --- a/internal/oidc/authcode/authcode.go +++ b/internal/oidc/authcode/authcode.go @@ -12,6 +12,7 @@ import ( log "github.com/sirupsen/logrus" "golang.org/x/oauth2" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/config" "github.com/oidc-mytoken/server/internal/db" "github.com/oidc-mytoken/server/internal/db/dbrepo/accesstokenrepo" @@ -23,7 +24,6 @@ import ( "github.com/oidc-mytoken/server/internal/model" "github.com/oidc-mytoken/server/internal/oidc/issuer" "github.com/oidc-mytoken/server/internal/server/routes" - "github.com/oidc-mytoken/server/pkg/api/v0" "github.com/oidc-mytoken/server/shared/context" pkgModel "github.com/oidc-mytoken/server/shared/model" mytoken "github.com/oidc-mytoken/server/shared/mytoken/pkg" diff --git a/internal/server/errorHandler.go b/internal/server/errorHandler.go index 828db6a5..b5bca0b2 100644 --- a/internal/server/errorHandler.go +++ b/internal/server/errorHandler.go @@ -6,8 +6,8 @@ import ( "github.com/gofiber/fiber/v2" fiberUtils "github.com/gofiber/fiber/v2/utils" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/model" - "github.com/oidc-mytoken/server/pkg/api/v0" ) func handleError(ctx *fiber.Ctx, err error) error { diff --git a/internal/server/server.go b/internal/server/server.go index e4a7ce26..b212d57e 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -12,6 +12,7 @@ import ( "github.com/gofiber/template/mustache" log "github.com/sirupsen/logrus" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/config" "github.com/oidc-mytoken/server/internal/endpoints" "github.com/oidc-mytoken/server/internal/endpoints/configuration" @@ -19,7 +20,6 @@ import ( "github.com/oidc-mytoken/server/internal/endpoints/redirect" "github.com/oidc-mytoken/server/internal/model" "github.com/oidc-mytoken/server/internal/server/routes" - "github.com/oidc-mytoken/server/pkg/api/v0" ) var server *fiber.App @@ -31,6 +31,7 @@ var serverConfig = fiber.Config{ ReadBufferSize: 8192, // WriteBufferSize: 4096, ErrorHandler: handleError, + // ProxyHeader is set later from config } //go:embed web/sites web/layouts @@ -62,6 +63,7 @@ func initTemplateEngine() { // Init initializes the server func Init() { initTemplateEngine() + serverConfig.ProxyHeader = config.Get().Server.ProxyHeader server = fiber.New(serverConfig) addMiddlewares(server) addRoutes(server) diff --git a/internal/utils/ctxUtils/networkData.go b/internal/utils/ctxUtils/networkData.go index 8a938f14..d3ef82cc 100644 --- a/internal/utils/ctxUtils/networkData.go +++ b/internal/utils/ctxUtils/networkData.go @@ -3,7 +3,7 @@ package ctxUtils import ( "github.com/gofiber/fiber/v2" - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" ) // ClientMetaData returns the model.ClientMetaData for a given fiber.Ctx diff --git a/internal/utils/ctxUtils/token.go b/internal/utils/ctxUtils/token.go index d1fe5fe6..3a2cb0d4 100644 --- a/internal/utils/ctxUtils/token.go +++ b/internal/utils/ctxUtils/token.go @@ -5,7 +5,7 @@ import ( "github.com/gofiber/fiber/v2" - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/shared/mytoken/token" ) diff --git a/pkg/api/v0/accessTokenRequest.go b/pkg/api/v0/accessTokenRequest.go deleted file mode 100644 index cd94c758..00000000 --- a/pkg/api/v0/accessTokenRequest.go +++ /dev/null @@ -1,11 +0,0 @@ -package api - -// AccessTokenRequest holds an request for an access token -type AccessTokenRequest struct { - Issuer string `json:"oidc_issuer,omitempty" form:"issuer" xml:"oidc_issuer"` - GrantType string `json:"grant_type" form:"grant_type" xml:"grant_type"` - Mytoken string `json:"mytoken" form:"mytoken" xml:"mytoken"` - Scope string `json:"scope,omitempty" form:"scope" xml:"scope"` - Audience string `json:"audience,omitempty" form:"audience" xml:"audience"` - Comment string `json:"comment,omitempty" form:"comment" xml:"comment"` -} diff --git a/pkg/api/v0/accessTokenResponse.go b/pkg/api/v0/accessTokenResponse.go deleted file mode 100644 index 358c3b87..00000000 --- a/pkg/api/v0/accessTokenResponse.go +++ /dev/null @@ -1,10 +0,0 @@ -package api - -// AccessTokenResponse is the response to a access token request -type AccessTokenResponse struct { - AccessToken string `json:"access_token"` - TokenType string `json:"token_type"` - ExpiresIn int64 `json:"expires_in"` - Scope string `json:"scope,omitempty"` - Audiences []string `json:"audience,omitempty"` -} diff --git a/pkg/api/v0/apiError.go b/pkg/api/v0/apiError.go deleted file mode 100644 index be1eeccc..00000000 --- a/pkg/api/v0/apiError.go +++ /dev/null @@ -1,47 +0,0 @@ -package api - -// APIError is an error object that is returned on the api when an error occurs -type APIError struct { - Error string `json:"error"` - ErrorDescription string `json:"error_description,omitempty"` -} - -// Predefined errors -var ( - APIErrorUnknownIssuer = APIError{ErrorInvalidRequest, "The provided issuer is not supported"} - APIErrorStateMismatch = APIError{ErrorInvalidRequest, "State mismatched"} - APIErrorUnsupportedOIDCFlow = APIError{ErrorInvalidGrant, "Unsupported oidc_flow"} - APIErrorUnsupportedGrantType = APIError{ErrorInvalidGrant, "Unsupported grant_type"} - APIErrorBadTransferCode = APIError{ErrorInvalidToken, "Bad polling or transfer code"} - APIErrorTransferCodeExpired = APIError{ErrorExpiredToken, "polling or transfer code is expired"} - APIErrorAuthorizationPending = APIError{ErrorAuthorizationPending, ""} - APIErrorConsentDeclined = APIError{ErrorAccessDenied, "user declined consent"} - APIErrorNoRefreshToken = APIError{ErrorOIDC, "Did not receive a refresh token"} - APIErrorInsufficientCapabilities = APIError{ErrorInsufficientCapabilities, "The provided token does not have the required capability for this operation"} - APIErrorUsageRestricted = APIError{ErrorUsageRestricted, "The restrictions of this token does not allow this usage"} - APIErrorNYI = APIError{ErrorNYI, ""} -) - -// Predefined OAuth2/OIDC errors -const ( - ErrorInvalidRequest = "invalid_request" - ErrorInvalidClient = "invalid_client" - ErrorInvalidGrant = "invalid_grant" - ErrorUnauthorizedClient = "unauthorized_client" - ErrorUnsupportedGrantType = "unsupported_grant_type" - ErrorInvalidScope = "invalid_scope" - ErrorInvalidToken = "invalid_token" - ErrorInsufficientScope = "insufficient_scope" - ErrorExpiredToken = "expired_token" - ErrorAccessDenied = "access_denied" - ErrorAuthorizationPending = "authorization_pending" -) - -// Additional Mytoken errors -const ( - ErrorInternal = "internal_server_error" - ErrorOIDC = "oidc_error" - ErrorNYI = "not_yet_implemented" - ErrorInsufficientCapabilities = "insufficient_capabilities" - ErrorUsageRestricted = "usage_restricted" -) diff --git a/pkg/api/v0/authCodeFlowResponse.go b/pkg/api/v0/authCodeFlowResponse.go deleted file mode 100644 index 80c706db..00000000 --- a/pkg/api/v0/authCodeFlowResponse.go +++ /dev/null @@ -1,14 +0,0 @@ -package api - -// AuthCodeFlowResponse is the response to an authorization code flow request -type AuthCodeFlowResponse struct { - AuthorizationURL string `json:"authorization_url"` - PollingInfo -} - -// PollingInfo holds all response information about polling codes -type PollingInfo struct { - PollingCode string `json:"polling_code,omitempty"` - PollingCodeExpiresIn int64 `json:"polling_code_expires_in,omitempty"` - PollingInterval int64 `json:"polling_interval,omitempty"` -} diff --git a/pkg/api/v0/authcodeFlowRequest.go b/pkg/api/v0/authcodeFlowRequest.go deleted file mode 100644 index e59acbd5..00000000 --- a/pkg/api/v0/authcodeFlowRequest.go +++ /dev/null @@ -1,19 +0,0 @@ -package api - -// AuthCodeFlowRequest holds a authorization code flow request -type AuthCodeFlowRequest struct { - OIDCFlowRequest - RedirectType string `json:"redirect_type"` -} - -// OIDCFlowRequest holds the request for an OIDC Flow request -type OIDCFlowRequest struct { - Issuer string `json:"oidc_issuer"` - GrantType string `json:"grant_type"` - OIDCFlow string `json:"oidc_flow"` - Restrictions Restrictions `json:"restrictions"` - Capabilities Capabilities `json:"capabilities"` - SubtokenCapabilities Capabilities `json:"subtoken_capabilities"` - Name string `json:"name"` - ResponseType string `json:"response_type"` -} diff --git a/pkg/api/v0/capability.go b/pkg/api/v0/capability.go deleted file mode 100644 index e2901966..00000000 --- a/pkg/api/v0/capability.go +++ /dev/null @@ -1,148 +0,0 @@ -package api - -import ( - "database/sql/driver" - "encoding/json" - "strings" -) - -// Defined Capabilities -var ( - CapabilityAT = Capability{ - Name: "AT", - Description: "Allows obtaining OpenID Connect Access Tokens.", - } - CapabilityCreateMT = Capability{ - Name: "create_mytoken", - Description: "Allows to create a new mytoken.", - } - CapabilitySettings = Capability{ - Name: "settings", - Description: "Allows to modify user settings.", - } - CapabilityTokeninfoIntrospect = Capability{ - Name: "tokeninfo_introspect", - Description: "Allows to obtain basic information about this token.", - } - CapabilityTokeninfoHistory = Capability{ - Name: "tokeninfo_history", - Description: "Allows to obtain the event history for this token.", - } - CapabilityTokeninfoTree = Capability{ - Name: "tokeninfo_tree", - Description: "Allows to list a subtoken-tree for this token.", - } - CapabilityListMT = Capability{ - Name: "list_mytokens", - Description: "Allows to list all mytokens.", - } -) - -// AllCapabilities holds all defined Capabilities -var AllCapabilities = Capabilities{ - CapabilityAT, - CapabilityCreateMT, - CapabilitySettings, - CapabilityTokeninfoIntrospect, - CapabilityTokeninfoHistory, - CapabilityTokeninfoTree, - CapabilityListMT, -} - -func descriptionFor(name string) string { - for _, c := range AllCapabilities { - if strings.EqualFold(c.Name, name) { - return c.Description - } - } - return "" -} - -// NewCapabilities casts a []string into Capabilities -func NewCapabilities(caps []string) (c Capabilities) { - for _, cc := range caps { - c = append(c, NewCapability(cc)) - } - return -} - -// NewCapability casts a string into a Capability -func NewCapability(name string) Capability { - return Capability{ - Name: name, - Description: descriptionFor(name), - } -} - -// Strings returns a slice of strings for these capabilities -func (c Capabilities) Strings() (s []string) { - for _, cc := range c { - s = append(s, cc.Name) - } - return -} - -// Capabilities is a slice of Capability -type Capabilities []Capability - -// Capability is a capability string -type Capability struct { - Name string - Description string -} - -// MarshalJSON implements the json.Marshaler interface -func (c Capability) MarshalJSON() ([]byte, error) { - return json.Marshal(c.Name) -} - -// UnmarshalJSON implements the json.Unmarshaler interface -func (c *Capability) UnmarshalJSON(data []byte) error { - var name string - if err := json.Unmarshal(data, &name); err != nil { - return err - } - *c = NewCapability(name) - return nil -} - -// Scan implements the sql.Scanner interface. -func (c *Capabilities) Scan(src interface{}) error { - if src == nil { - return nil - } - val := src.([]uint8) - err := json.Unmarshal(val, &c) - return err -} - -// Value implements the driver.Valuer interface -func (c Capabilities) Value() (driver.Value, error) { - if len(c) == 0 { - return nil, nil - } - return json.Marshal(c) -} - -// Tighten tightens two set of Capabilities into one new -func Tighten(a, b Capabilities) (res Capabilities) { - if b == nil { - return a - } - for _, bb := range b { - if a.Has(bb) { - res = append(res, bb) - } - } - return -} - -// Has checks if Capabilities slice contains the passed Capability -func (c Capabilities) Has(a Capability) bool { - for _, cc := range c { - if cc == a { - return true - } - } - return false -} diff --git a/pkg/api/v0/capability_test.go b/pkg/api/v0/capability_test.go deleted file mode 100644 index 48a377fa..00000000 --- a/pkg/api/v0/capability_test.go +++ /dev/null @@ -1,58 +0,0 @@ -package api - -import "testing" - -func fail(t *testing.T, expected, got Capabilities) { - t.Errorf("Expected '%v', got '%v'", expected, got) -} - -func testTighten(t *testing.T, a, b, expected Capabilities) { - intersect := Tighten(a, b) - if len(intersect) != len(expected) { - fail(t, expected, intersect) - } - for i, ee := range expected { - if ee != intersect[i] { - fail(t, expected, intersect) - } - } -} - -func TestTightenAllEmpty(t *testing.T) { - a := Capabilities{} - b := Capabilities{} - expected := Capabilities{} - testTighten(t, a, b, expected) -} -func TestTightenOneEmpty(t *testing.T) { - a := Capabilities{} - b := NewCapabilities([]string{"not", "empty"}) - expected := Capabilities{} - testTighten(t, a, b, expected) - testTighten(t, b, a, expected) -} -func TestTightenNoIntersection(t *testing.T) { - a := NewCapabilities([]string{"some", "values"}) - b := NewCapabilities([]string{"completly", "different"}) - expected := Capabilities{} - testTighten(t, a, b, expected) - testTighten(t, b, a, expected) -} -func TestTightenSame(t *testing.T) { - a := NewCapabilities([]string{"some", "values"}) - testTighten(t, a, a, a) -} -func TestTightenSomeIntersection(t *testing.T) { - a := NewCapabilities([]string{"some", "values"}) - b := NewCapabilities([]string{"some", "different"}) - expected := NewCapabilities([]string{"some"}) - testTighten(t, a, b, expected) - testTighten(t, b, a, expected) -} -func TestTightenSubSet(t *testing.T) { - a := NewCapabilities([]string{"some", "values"}) - b := NewCapabilities([]string{"some", "more", "values"}) - expected := NewCapabilities([]string{"some", "values"}) - testTighten(t, a, b, expected) - testTighten(t, b, a, expected) -} diff --git a/pkg/api/v0/clientMetaData.go b/pkg/api/v0/clientMetaData.go deleted file mode 100644 index 6fb81e2e..00000000 --- a/pkg/api/v0/clientMetaData.go +++ /dev/null @@ -1,7 +0,0 @@ -package api - -// ClientMetaData hold information about the calling client -type ClientMetaData struct { - IP string `db:"ip" json:"ip,omitempty"` - UserAgent string `db:"user_agent" json:"user_agent,omitempty"` -} diff --git a/pkg/api/v0/eventHistory.go b/pkg/api/v0/eventHistory.go deleted file mode 100644 index c8860cce..00000000 --- a/pkg/api/v0/eventHistory.go +++ /dev/null @@ -1,10 +0,0 @@ -package api - -type EventHistory []EventEntry - -type EventEntry struct { - Event string `db:"event" json:"event"` - Time int64 `db:"time" json:"time"` - Comment string `db:"comment" json:"comment,omitempty"` - ClientMetaData `json:",inline"` -} diff --git a/pkg/api/v0/grantTypes.go b/pkg/api/v0/grantTypes.go deleted file mode 100644 index 85178f70..00000000 --- a/pkg/api/v0/grantTypes.go +++ /dev/null @@ -1,13 +0,0 @@ -package api - -var AllGrantTypes = [...]string{GrantTypeMytoken, GrantTypeOIDCFlow, GrantTypePollingCode, GrantTypeAccessToken, GrantTypePrivateKeyJWT, GrantTypeTransferCode} - -// GrantTypes -const ( - GrantTypeMytoken = "mytoken" - GrantTypeOIDCFlow = "oidc_flow" - GrantTypePollingCode = "polling_code" - GrantTypeAccessToken = "access_token" - GrantTypePrivateKeyJWT = "private_key_jwt" - GrantTypeTransferCode = "transfer_code" -) diff --git a/pkg/api/v0/mytoken.go b/pkg/api/v0/mytoken.go deleted file mode 100644 index 81f123de..00000000 --- a/pkg/api/v0/mytoken.go +++ /dev/null @@ -1,67 +0,0 @@ -package api - -import ( - "encoding/json" - "fmt" -) - -// Mytoken is a mytoken Mytoken -type Mytoken struct { - Version TokenVersion `json:"ver"` - Issuer string `json:"iss"` - Subject string `json:"sub"` - ExpiresAt int64 `json:"exp,omitempty"` - NotBefore int64 `json:"nbf"` - IssuedAt int64 `json:"iat"` - ID string `json:"jti"` - SeqNo uint64 `json:"seq_no"` - Audience string `json:"aud"` - OIDCSubject string `json:"oidc_sub"` - OIDCIssuer string `json:"oidc_iss"` - Restrictions Restrictions `json:"restrictions,omitempty"` - Capabilities Capabilities `json:"capabilities"` - SubtokenCapabilities Capabilities `json:"subtoken_capabilities,omitempty"` - Rotation Rotation `json:"rotation,omitempty"` -} - -var TokenVer = TokenVersion{ - Major: 0, - Minor: 1, -} - -// UsedMytoken is a type for a Mytoken that has been used, it additionally has information how often it has been used -type UsedMytoken struct { - Mytoken `json:",inline"` - Restrictions []UsedRestriction `json:"restrictions,omitempty"` -} - -type Rotation struct { - OnAT bool `json:"on_AT,omitempty"` - OnOther bool `json:"on_other,omitempty"` - Lifetime uint64 `json:"lifetime,omitempty"` -} - -type TokenVersion struct { - Major int - Minor int -} - -const vFmt = "%d.%d" - -func (v TokenVersion) String() string { - return fmt.Sprintf(vFmt, v.Major, v.Minor) -} -func (v TokenVersion) Version() string { - return v.String() -} -func (v TokenVersion) MarshalJSON() ([]byte, error) { - return json.Marshal(v.String()) -} -func (v *TokenVersion) UnmarshalJSON(data []byte) error { - var str string - if err := json.Unmarshal(data, &str); err != nil { - return err - } - _, err := fmt.Sscanf(str, vFmt, &v.Major, &v.Minor) - return err -} diff --git a/pkg/api/v0/mytokenConfiguration.go b/pkg/api/v0/mytokenConfiguration.go deleted file mode 100644 index 49a7e94f..00000000 --- a/pkg/api/v0/mytokenConfiguration.go +++ /dev/null @@ -1,28 +0,0 @@ -package api - -// MytokenConfiguration holds information about a mytoken instance -type MytokenConfiguration struct { - Issuer string `json:"issuer"` - AccessTokenEndpoint string `json:"access_token_endpoint"` - MytokenEndpoint string `json:"mytoken_endpoint"` - TokeninfoEndpoint string `json:"tokeninfo_endpoint,omitempty"` - RevocationEndpoint string `json:"revocation_endpoint,omitempty"` - UserSettingsEndpoint string `json:"usersettings_endpoint"` - TokenTransferEndpoint string `json:"token_transfer_endpoint,omitempty"` - JWKSURI string `json:"jwks_uri"` - ProvidersSupported []SupportedProviderConfig `json:"providers_supported"` - TokenSigningAlgValue string `json:"token_signing_alg_value"` - TokenInfoEndpointActionsSupported []string `json:"tokeninfo_endpoint_actions_supported,omitempty"` - AccessTokenEndpointGrantTypesSupported []string `json:"access_token_endpoint_grant_types_supported"` - MytokenEndpointGrantTypesSupported []string `json:"mytoken_endpoint_grant_types_supported"` - MytokenEndpointOIDCFlowsSupported []string `json:"mytoken_endpoint_oidc_flows_supported"` - ResponseTypesSupported []string `json:"response_types_supported"` - ServiceDocumentation string `json:"service_documentation,omitempty"` - Version string `json:"version,omitempty"` -} - -// SupportedProviderConfig holds information about a provider -type SupportedProviderConfig struct { - Issuer string `json:"issuer"` - ScopesSupported []string `json:"scopes_supported"` -} diff --git a/pkg/api/v0/mytokenEntry.go b/pkg/api/v0/mytokenEntry.go deleted file mode 100644 index 05c5d972..00000000 --- a/pkg/api/v0/mytokenEntry.go +++ /dev/null @@ -1,15 +0,0 @@ -package api - -// MytokenEntry holds the information of a MytokenEntry as stored in the -// database -type MytokenEntry struct { - Name string `json:"name,omitempty"` - CreatedAt int64 `json:"created"` - ClientMetaData `json:",inline"` -} - -// MytokenEntryTree is a tree of MytokenEntry -type MytokenEntryTree struct { - Token MytokenEntry `json:"token"` - Children []MytokenEntryTree `json:"children,omitempty"` -} diff --git a/pkg/api/v0/mytokenRequest.go b/pkg/api/v0/mytokenRequest.go deleted file mode 100644 index 9e4fccaa..00000000 --- a/pkg/api/v0/mytokenRequest.go +++ /dev/null @@ -1,14 +0,0 @@ -package api - -// MytokenFromMytokenRequest is a request to create a new Mytoken from an existing Mytoken -type MytokenFromMytokenRequest struct { - Issuer string `json:"oidc_issuer"` - GrantType string `json:"grant_type"` - Mytoken string `json:"mytoken"` - Restrictions Restrictions `json:"restrictions"` - Capabilities Capabilities `json:"capabilities"` - SubtokenCapabilities Capabilities `json:"subtoken_capabilities"` - Name string `json:"name"` - ResponseType string `json:"response_type"` - FailOnRestrictionsNotTighter bool `json:"error_on_restrictions"` -} diff --git a/pkg/api/v0/mytokenResponse.go b/pkg/api/v0/mytokenResponse.go deleted file mode 100644 index 11c6e519..00000000 --- a/pkg/api/v0/mytokenResponse.go +++ /dev/null @@ -1,12 +0,0 @@ -package api - -// MytokenResponse is a response to a mytoken request -type MytokenResponse struct { - Mytoken string `json:"mytoken,omitempty"` - MytokenType string `json:"mytoken_type"` - TransferCode string `json:"transfer_code,omitempty"` - ExpiresIn uint64 `json:"expires_in,omitempty"` - Restrictions Restrictions `json:"restrictions,omitempty"` - Capabilities Capabilities `json:"capabilities,omitempty"` - SubtokenCapabilities Capabilities `json:"subtoken_capabilities,omitempty"` -} diff --git a/pkg/api/v0/oidcFlow.go b/pkg/api/v0/oidcFlow.go deleted file mode 100644 index 41a3b5b1..00000000 --- a/pkg/api/v0/oidcFlow.go +++ /dev/null @@ -1,7 +0,0 @@ -package api - -// OIDCFlows -const ( - OIDCFlowAuthorizationCode = "authorization_code" - // OIDCFlowDevice = "device" -) diff --git a/pkg/api/v0/pollingCodeRequest.go b/pkg/api/v0/pollingCodeRequest.go deleted file mode 100644 index 27c55801..00000000 --- a/pkg/api/v0/pollingCodeRequest.go +++ /dev/null @@ -1,7 +0,0 @@ -package api - -// PollingCodeRequest is a polling code request -type PollingCodeRequest struct { - GrantType string `json:"grant_type"` - PollingCode string `json:"polling_code"` -} diff --git a/pkg/api/v0/responseType.go b/pkg/api/v0/responseType.go deleted file mode 100644 index 7127e921..00000000 --- a/pkg/api/v0/responseType.go +++ /dev/null @@ -1,8 +0,0 @@ -package api - -// ResponseTypes -const ( - ResponseTypeToken = "token" - ResponseTypeShortToken = "short_token" - ResponseTypeTransferCode = "transfer_code" -) diff --git a/pkg/api/v0/restrictions.go b/pkg/api/v0/restrictions.go deleted file mode 100644 index 574cd13b..00000000 --- a/pkg/api/v0/restrictions.go +++ /dev/null @@ -1,24 +0,0 @@ -package api - -// Restrictions is a slice of Restriction -type Restrictions []Restriction - -// Restriction describes a token usage restriction -type Restriction struct { - NotBefore int64 `json:"nbf,omitempty"` - ExpiresAt int64 `json:"exp,omitempty"` - Scope string `json:"scope,omitempty"` - Audiences []string `json:"audience,omitempty"` - IPs []string `json:"ip,omitempty"` - GeoIPAllow []string `json:"geoip_allow,omitempty"` - GeoIPDisallow []string `json:"geoip_disallow,omitempty"` - UsagesAT *int64 `json:"usages_AT,omitempty"` - UsagesOther *int64 `json:"usages_other,omitempty"` -} - -// UsedRestriction is a type for a restriction that has been used and additionally has information how often is has been used -type UsedRestriction struct { - Restriction `json:",inline"` - UsagesATDone *int64 `json:"usages_AT_done,omitempty"` - UsagesOtherDone *int64 `json:"usages_other_done,omitempty"` -} diff --git a/pkg/api/v0/revocationRequest.go b/pkg/api/v0/revocationRequest.go deleted file mode 100644 index 0d04c8b1..00000000 --- a/pkg/api/v0/revocationRequest.go +++ /dev/null @@ -1,8 +0,0 @@ -package api - -// RevocationRequest holds the information for a token revocation request -type RevocationRequest struct { - Token string `json:"token"` // We don't use model.Token here because we need to revoke a short token differently - Recursive bool `json:"recursive,omitempty"` - OIDCIssuer string `json:"oidc_issuer,omitempty"` -} diff --git a/pkg/api/v0/tokeninfoAction.go b/pkg/api/v0/tokeninfoAction.go deleted file mode 100644 index a2388a31..00000000 --- a/pkg/api/v0/tokeninfoAction.go +++ /dev/null @@ -1,12 +0,0 @@ -package api - -// AllTokeninfoActions holds all defined TokenInfo strings -var AllTokeninfoActions = [...]string{TokeninfoActionIntrospect, TokeninfoActionEventHistory, TokeninfoActionSubtokenTree, TokeninfoActionListMytokens} - -// TokeninfoActions -const ( - TokeninfoActionIntrospect = "introspect" - TokeninfoActionEventHistory = "event_history" - TokeninfoActionSubtokenTree = "subtoken_tree" - TokeninfoActionListMytokens = "list_mytokens" -) diff --git a/pkg/api/v0/tokeninfoRequest.go b/pkg/api/v0/tokeninfoRequest.go deleted file mode 100644 index 6e0a9331..00000000 --- a/pkg/api/v0/tokeninfoRequest.go +++ /dev/null @@ -1,6 +0,0 @@ -package api - -type TokenInfoRequest struct { - Action string `json:"action"` - Mytoken string `json:"mytoken"` -} diff --git a/pkg/api/v0/tokeninfoResponses.go b/pkg/api/v0/tokeninfoResponses.go deleted file mode 100644 index 9c128314..00000000 --- a/pkg/api/v0/tokeninfoResponses.go +++ /dev/null @@ -1,17 +0,0 @@ -package api - -type TokeninfoIntrospectResponse struct { - Valid bool `json:"valid"` - Token UsedMytoken `json:"token"` -} - -type TokeninfoHistoryResponse struct { - EventHistory EventHistory `json:"events"` -} - -type TokeninfoTreeResponse struct { - Tokens MytokenEntryTree `json:"mytokens"` -} -type TokeninfoListResponse struct { - Tokens []MytokenEntryTree `json:"mytokens"` -} diff --git a/pkg/api/v0/transfercodeRequest.go b/pkg/api/v0/transfercodeRequest.go deleted file mode 100644 index 155a4444..00000000 --- a/pkg/api/v0/transfercodeRequest.go +++ /dev/null @@ -1,12 +0,0 @@ -package api - -// CreateTransferCodeRequest is a request to create a new transfer code from an existing mytoken -type CreateTransferCodeRequest struct { - Mytoken string `json:"mytoken"` // we use string and not token.Token because the token can also be in the Auth Header and there it is a string -} - -// ExchangeTransferCodeRequest is a request to exchange a transfer code for the mytoken -type ExchangeTransferCodeRequest struct { - GrantType string `json:"grant_type"` - TransferCode string `json:"transfer_code"` -} diff --git a/pkg/api/v0/transfercodeResponse.go b/pkg/api/v0/transfercodeResponse.go deleted file mode 100644 index 5ec1fb5e..00000000 --- a/pkg/api/v0/transfercodeResponse.go +++ /dev/null @@ -1,8 +0,0 @@ -package api - -// TransferCodeResponse is the response to a transfer code request -type TransferCodeResponse struct { - MytokenType string `json:"mytoken_type"` - TransferCode string `json:"transfer_code"` - ExpiresIn uint64 `json:"expires_in"` -} diff --git a/shared/model/apiError.go b/shared/model/apiError.go index 2911cb1a..927a1837 100644 --- a/shared/model/apiError.go +++ b/shared/model/apiError.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" ) // InternalServerError creates an APIError for internal server errors diff --git a/shared/model/grantType.go b/shared/model/grantType.go index 4fb27739..67792f78 100644 --- a/shared/model/grantType.go +++ b/shared/model/grantType.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" yaml "gopkg.in/yaml.v3" ) diff --git a/shared/model/oidcFlow.go b/shared/model/oidcFlow.go index 7005fdf9..39b83786 100644 --- a/shared/model/oidcFlow.go +++ b/shared/model/oidcFlow.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" yaml "gopkg.in/yaml.v3" ) diff --git a/shared/model/responseType.go b/shared/model/responseType.go index 48e1a0fc..3d93d7ce 100644 --- a/shared/model/responseType.go +++ b/shared/model/responseType.go @@ -6,7 +6,7 @@ import ( "encoding/json" "fmt" - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" yaml "gopkg.in/yaml.v3" ) diff --git a/shared/model/tokeninfoAction.go b/shared/model/tokeninfoAction.go index 45d44bf2..93a63315 100644 --- a/shared/model/tokeninfoAction.go +++ b/shared/model/tokeninfoAction.go @@ -4,7 +4,7 @@ import ( "encoding/json" "fmt" - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" yaml "gopkg.in/yaml.v3" ) diff --git a/shared/mytoken/event/event.go b/shared/mytoken/event/event.go index 6c028a64..b87e6464 100644 --- a/shared/mytoken/event/event.go +++ b/shared/mytoken/event/event.go @@ -3,8 +3,8 @@ package event import ( "github.com/jmoiron/sqlx" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/db/dbrepo/eventrepo" - "github.com/oidc-mytoken/server/pkg/api/v0" pkg "github.com/oidc-mytoken/server/shared/mytoken/event/pkg" "github.com/oidc-mytoken/server/shared/mytoken/pkg/mtid" ) diff --git a/shared/mytoken/mytokenHandler.go b/shared/mytoken/mytokenHandler.go index b3ed5fa4..675fb9d3 100644 --- a/shared/mytoken/mytokenHandler.go +++ b/shared/mytoken/mytokenHandler.go @@ -11,6 +11,7 @@ import ( "github.com/jmoiron/sqlx" log "github.com/sirupsen/logrus" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/config" "github.com/oidc-mytoken/server/internal/db" "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo" @@ -22,7 +23,6 @@ import ( "github.com/oidc-mytoken/server/internal/oidc/revoke" "github.com/oidc-mytoken/server/internal/server/httpStatus" "github.com/oidc-mytoken/server/internal/utils/ctxUtils" - "github.com/oidc-mytoken/server/pkg/api/v0" pkgModel "github.com/oidc-mytoken/server/shared/model" eventService "github.com/oidc-mytoken/server/shared/mytoken/event" event "github.com/oidc-mytoken/server/shared/mytoken/event/pkg" diff --git a/shared/mytoken/pkg/mytoken.go b/shared/mytoken/pkg/mytoken.go index fd1de442..5cd9c3ac 100644 --- a/shared/mytoken/pkg/mytoken.go +++ b/shared/mytoken/pkg/mytoken.go @@ -6,12 +6,12 @@ import ( jwt "github.com/dgrijalva/jwt-go" "github.com/jmoiron/sqlx" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/config" "github.com/oidc-mytoken/server/internal/db" "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/transfercoderepo" response "github.com/oidc-mytoken/server/internal/endpoints/token/mytoken/pkg" "github.com/oidc-mytoken/server/internal/jws" - "github.com/oidc-mytoken/server/pkg/api/v0" "github.com/oidc-mytoken/server/shared/model" eventService "github.com/oidc-mytoken/server/shared/mytoken/event" event "github.com/oidc-mytoken/server/shared/mytoken/event/pkg" diff --git a/shared/mytoken/restrictions/restriction.go b/shared/mytoken/restrictions/restriction.go index a2887e16..1b8b4a79 100644 --- a/shared/mytoken/restrictions/restriction.go +++ b/shared/mytoken/restrictions/restriction.go @@ -11,10 +11,10 @@ import ( "github.com/oidc-mytoken/server/internal/config" log "github.com/sirupsen/logrus" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/mytokenrepohelper" "github.com/oidc-mytoken/server/internal/utils/geoip" "github.com/oidc-mytoken/server/internal/utils/hashUtils" - "github.com/oidc-mytoken/server/pkg/api/v0" "github.com/oidc-mytoken/server/shared/mytoken/pkg/mtid" "github.com/oidc-mytoken/server/shared/utils" "github.com/oidc-mytoken/server/shared/utils/unixtime" diff --git a/shared/mytoken/restrictions/restriction_test.go b/shared/mytoken/restrictions/restriction_test.go index 05f25ed7..5d7e36a8 100644 --- a/shared/mytoken/restrictions/restriction_test.go +++ b/shared/mytoken/restrictions/restriction_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/shared/utils" "github.com/oidc-mytoken/server/shared/utils/unixtime" ) diff --git a/shared/mytoken/rotation/rotation.go b/shared/mytoken/rotation/rotation.go index 6a5c1c0b..e85b02fc 100644 --- a/shared/mytoken/rotation/rotation.go +++ b/shared/mytoken/rotation/rotation.go @@ -1,7 +1,7 @@ package rotation import ( - "github.com/oidc-mytoken/server/pkg/api/v0" + "github.com/oidc-mytoken/api/v0" ) type Rotation api.Rotation From e206b048ccf7c499d9fb24c051cfe134e9752e82 Mon Sep 17 00:00:00 2001 From: zachmann Date: Thu, 10 Jun 2021 15:25:26 +0200 Subject: [PATCH 12/61] add token type to mytoken --- go.mod | 2 +- go.sum | 3 ++- shared/mytoken/pkg/mytoken.go | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index 23d86023..24d6ca03 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/jinzhu/copier v0.1.0 github.com/jmoiron/sqlx v1.3.1 github.com/lestrrat-go/jwx v1.0.8 - github.com/oidc-mytoken/api v0.0.0-20210604072940-156be40bab89 + github.com/oidc-mytoken/api v0.0.0-20210610132321-76897d5a4e26 github.com/oidc-mytoken/lib v0.2.1-0.20210604073533-566d9af1210e github.com/satori/go.uuid v1.2.0 github.com/sirupsen/logrus v1.7.0 diff --git a/go.sum b/go.sum index d2148525..014c0133 100644 --- a/go.sum +++ b/go.sum @@ -388,8 +388,9 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nishanths/exhaustive v0.0.0-20200525081945-8e46705b6132 h1:NjznefjSrral0MiR4KlB41io/d3OklvhcgQUdfZTqJE= github.com/nishanths/exhaustive v0.0.0-20200525081945-8e46705b6132/go.mod h1:wBEpHwM2OdmeNpdCvRPUlkEbBuaFmcK4Wv8Q7FuGW3c= -github.com/oidc-mytoken/api v0.0.0-20210604072940-156be40bab89 h1:sCjNUJwCUo+8mp/DTQxxUes4/Uchf76oS8+mDqzGQTU= github.com/oidc-mytoken/api v0.0.0-20210604072940-156be40bab89/go.mod h1:S8t1XA42EFAgc3vUfis0g1LPGA4TXH0nfDynvgo6cwk= +github.com/oidc-mytoken/api v0.0.0-20210610132321-76897d5a4e26 h1:chFG84gu/7yNiv20s8VFgrfOXJtzWGPrU2ueE7l7g2g= +github.com/oidc-mytoken/api v0.0.0-20210610132321-76897d5a4e26/go.mod h1:S8t1XA42EFAgc3vUfis0g1LPGA4TXH0nfDynvgo6cwk= github.com/oidc-mytoken/lib v0.2.1-0.20210604073533-566d9af1210e h1:UHswGI8MNr4dUh98rNOiNfNQHUK44va0d6wQwcFGa2c= github.com/oidc-mytoken/lib v0.2.1-0.20210604073533-566d9af1210e/go.mod h1:vjpAk3Fn5Ow0MJMfy5lmLox29zQ89rhTNuvGjf/ql54= github.com/oidc-mytoken/server v0.2.0/go.mod h1:6uFm+Za9NMK3gq4OOIeX3gs3T6leluVIWsGiM1zlQbA= diff --git a/shared/mytoken/pkg/mytoken.go b/shared/mytoken/pkg/mytoken.go index 5cd9c3ac..a846c073 100644 --- a/shared/mytoken/pkg/mytoken.go +++ b/shared/mytoken/pkg/mytoken.go @@ -26,6 +26,7 @@ import ( type Mytoken struct { // On update also update api.Mytoken Version api.TokenVersion `json:"ver"` + TokenType string `json:"token_type"` Issuer string `json:"iss"` Subject string `json:"sub"` ExpiresAt unixtime.UnixTime `json:"exp,omitempty"` @@ -75,6 +76,7 @@ func NewMytoken(oidcSub, oidcIss string, r restrictions.Restrictions, c, sc api. now := unixtime.Now() mt := &Mytoken{ Version: api.TokenVer, + TokenType: api.TokenType, ID: mtid.New(), SeqNo: 1, IssuedAt: now, From 35b37fe88c3f2bf6b437b52d3448ffc5c7da2005 Mon Sep 17 00:00:00 2001 From: zachmann Date: Thu, 10 Jun 2021 17:08:48 +0200 Subject: [PATCH 13/61] add option to disable individual restriction keys --- config/example-config.yaml | 12 ++ docker/example-docker-config.yaml | 12 ++ go.mod | 2 +- go.sum | 4 +- internal/config/config.go | 20 +-- .../configuration/configurationEndpoint.go | 1 + .../configuration/pkg/mytokenConfiguration.go | 2 + internal/endpoints/consent/consent.go | 2 +- .../token/access/accessTokenEndpoint.go | 10 +- .../token/mytoken/mytokenEndpoint.go | 6 +- .../token/mytoken/polling/pollingEndpoint.go | 8 +- internal/endpoints/tokeninfo/history.go | 4 +- internal/endpoints/tokeninfo/introspect.go | 2 +- internal/endpoints/tokeninfo/list.go | 4 +- internal/endpoints/tokeninfo/tree.go | 4 +- internal/model/response.go | 2 +- internal/model/restrictionKey.go | 122 ++++++++++++++++++ internal/oidc/authcode/authcode.go | 11 +- internal/server/errorHandler.go | 2 +- internal/server/server.go | 2 +- shared/model/apiError.go | 50 +++---- shared/mytoken/mytokenHandler.go | 15 ++- shared/mytoken/restrictions/restriction.go | 75 ++++++++++- 23 files changed, 298 insertions(+), 74 deletions(-) create mode 100644 internal/model/restrictionKey.go diff --git a/config/example-config.yaml b/config/example-config.yaml index aa8fdf14..690efaa8 100644 --- a/config/example-config.yaml +++ b/config/example-config.yaml @@ -82,6 +82,18 @@ features: oidc_flows: - "authorization_code" # Always enabled + # Specify restriction keys to disable support for them; on default all restriction keys are supported. + unsupported_restrictions: +# - nbf +# - exp +# - scope +# - audience +# - ip +# - geoip_allow +# - geoip_disallow +# - usages_AT +# - usages_other + # Revocation for tokens issued by mytoken. Only disable this if you have good reasons for it. token_revocation: enabled: true diff --git a/docker/example-docker-config.yaml b/docker/example-docker-config.yaml index a365cb86..8d013036 100644 --- a/docker/example-docker-config.yaml +++ b/docker/example-docker-config.yaml @@ -84,6 +84,18 @@ features: oidc_flows: - "authorization_code" # Always enabled + # Specify restriction keys to disable support for them; on default all restriction keys are supported. + unsupported_restrictions: +# - nbf +# - exp +# - scope +# - audience +# - ip +# - geoip_allow +# - geoip_disallow +# - usages_AT +# - usages_other + # Revocation for tokens issued by mytoken. Only disable this if you have good reasons for it. token_revocation: enabled: true diff --git a/go.mod b/go.mod index 24d6ca03..3c85b8e6 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/jinzhu/copier v0.1.0 github.com/jmoiron/sqlx v1.3.1 github.com/lestrrat-go/jwx v1.0.8 - github.com/oidc-mytoken/api v0.0.0-20210610132321-76897d5a4e26 + github.com/oidc-mytoken/api v0.0.0-20210610140005-29f166a942ec github.com/oidc-mytoken/lib v0.2.1-0.20210604073533-566d9af1210e github.com/satori/go.uuid v1.2.0 github.com/sirupsen/logrus v1.7.0 diff --git a/go.sum b/go.sum index 014c0133..66561672 100644 --- a/go.sum +++ b/go.sum @@ -389,8 +389,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/nishanths/exhaustive v0.0.0-20200525081945-8e46705b6132 h1:NjznefjSrral0MiR4KlB41io/d3OklvhcgQUdfZTqJE= github.com/nishanths/exhaustive v0.0.0-20200525081945-8e46705b6132/go.mod h1:wBEpHwM2OdmeNpdCvRPUlkEbBuaFmcK4Wv8Q7FuGW3c= github.com/oidc-mytoken/api v0.0.0-20210604072940-156be40bab89/go.mod h1:S8t1XA42EFAgc3vUfis0g1LPGA4TXH0nfDynvgo6cwk= -github.com/oidc-mytoken/api v0.0.0-20210610132321-76897d5a4e26 h1:chFG84gu/7yNiv20s8VFgrfOXJtzWGPrU2ueE7l7g2g= -github.com/oidc-mytoken/api v0.0.0-20210610132321-76897d5a4e26/go.mod h1:S8t1XA42EFAgc3vUfis0g1LPGA4TXH0nfDynvgo6cwk= +github.com/oidc-mytoken/api v0.0.0-20210610140005-29f166a942ec h1:DGLeZJnsVWq6y81d6AH9mo+J5dQO47VFH9K2bKYgAjk= +github.com/oidc-mytoken/api v0.0.0-20210610140005-29f166a942ec/go.mod h1:S8t1XA42EFAgc3vUfis0g1LPGA4TXH0nfDynvgo6cwk= github.com/oidc-mytoken/lib v0.2.1-0.20210604073533-566d9af1210e h1:UHswGI8MNr4dUh98rNOiNfNQHUK44va0d6wQwcFGa2c= github.com/oidc-mytoken/lib v0.2.1-0.20210604073533-566d9af1210e/go.mod h1:vjpAk3Fn5Ow0MJMfy5lmLox29zQ89rhTNuvGjf/ql54= github.com/oidc-mytoken/server v0.2.0/go.mod h1:6uFm+Za9NMK3gq4OOIeX3gs3T6leluVIWsGiM1zlQbA= diff --git a/internal/config/config.go b/internal/config/config.go index 29464784..167d761c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/coreos/go-oidc/v3/oidc" + model2 "github.com/oidc-mytoken/server/internal/model" log "github.com/sirupsen/logrus" yaml "gopkg.in/yaml.v3" @@ -102,15 +103,16 @@ type apiConf struct { } type featuresConf struct { - EnabledOIDCFlows []model.OIDCFlow `yaml:"enabled_oidc_flows"` - TokenRevocation onlyEnable `yaml:"token_revocation"` - ShortTokens shortTokenConfig `yaml:"short_tokens"` - TransferCodes onlyEnable `yaml:"transfer_codes"` - Polling pollingConf `yaml:"polling_codes"` - AccessTokenGrant onlyEnable `yaml:"access_token_grant"` - SignedJWTGrant onlyEnable `yaml:"signed_jwt_grant"` - TokenInfo tokeninfoConfig `yaml:"tokeninfo"` - WebInterface onlyEnable `yaml:"web_interface"` + EnabledOIDCFlows []model.OIDCFlow `yaml:"enabled_oidc_flows"` + TokenRevocation onlyEnable `yaml:"token_revocation"` + ShortTokens shortTokenConfig `yaml:"short_tokens"` + TransferCodes onlyEnable `yaml:"transfer_codes"` + Polling pollingConf `yaml:"polling_codes"` + AccessTokenGrant onlyEnable `yaml:"access_token_grant"` + SignedJWTGrant onlyEnable `yaml:"signed_jwt_grant"` + TokenInfo tokeninfoConfig `yaml:"tokeninfo"` + WebInterface onlyEnable `yaml:"web_interface"` + DisabledRestrictionKeys model2.RestrictionKeys `yaml:"unsupported_restrictions"` } type tokeninfoConfig struct { diff --git a/internal/endpoints/configuration/configurationEndpoint.go b/internal/endpoints/configuration/configurationEndpoint.go index aa5d04c7..9a5e2fa4 100644 --- a/internal/endpoints/configuration/configurationEndpoint.go +++ b/internal/endpoints/configuration/configurationEndpoint.go @@ -67,6 +67,7 @@ func basicConfiguration() *pkg.MytokenConfiguration { MytokenEndpointOIDCFlowsSupported: config.Get().Features.EnabledOIDCFlows, ResponseTypesSupported: []pkgModel.ResponseType{pkgModel.ResponseTypeToken}, TokenEndpoint: utils.CombineURLPath(config.Get().IssuerURL, apiPaths.AccessTokenEndpoint), + SupportedRestrictionKeys: model.AllRestrictionKeys.Disable(config.Get().Features.DisabledRestrictionKeys), } } diff --git a/internal/endpoints/configuration/pkg/mytokenConfiguration.go b/internal/endpoints/configuration/pkg/mytokenConfiguration.go index 1e8dc536..36cb5d43 100644 --- a/internal/endpoints/configuration/pkg/mytokenConfiguration.go +++ b/internal/endpoints/configuration/pkg/mytokenConfiguration.go @@ -2,6 +2,7 @@ package pkg import ( "github.com/oidc-mytoken/api/v0" + model2 "github.com/oidc-mytoken/server/internal/model" "github.com/oidc-mytoken/server/shared/model" ) @@ -13,5 +14,6 @@ type MytokenConfiguration struct { MytokenEndpointGrantTypesSupported []model.GrantType `json:"mytoken_endpoint_grant_types_supported"` MytokenEndpointOIDCFlowsSupported []model.OIDCFlow `json:"mytoken_endpoint_oidc_flows_supported"` ResponseTypesSupported []model.ResponseType `json:"response_types_supported"` + SupportedRestrictionKeys model2.RestrictionKeys `json:"supported_restriction_keys"` TokenEndpoint string `json:"token_endpoint"` // For compatibility with OIDC } diff --git a/internal/endpoints/consent/consent.go b/internal/endpoints/consent/consent.go index e4d98725..9837036e 100644 --- a/internal/endpoints/consent/consent.go +++ b/internal/endpoints/consent/consent.go @@ -115,7 +115,7 @@ func HandleConsentPost(ctx *fiber.Ctx) error { if !ok { return model.Response{ Status: fiber.StatusBadRequest, - Response: api.APIErrorUnknownIssuer, + Response: api.ErrorUnknownIssuer, }.Send(ctx) } authURL := authcode.GetAuthorizationURL(provider, oState.State(), req.Restrictions) diff --git a/internal/endpoints/token/access/accessTokenEndpoint.go b/internal/endpoints/token/access/accessTokenEndpoint.go index 4844483f..cf6efca4 100644 --- a/internal/endpoints/token/access/accessTokenEndpoint.go +++ b/internal/endpoints/token/access/accessTokenEndpoint.go @@ -42,7 +42,7 @@ func HandleAccessTokenEndpoint(ctx *fiber.Ctx) error { if req.GrantType != model.GrantTypeMytoken { res := serverModel.Response{ Status: fiber.StatusBadRequest, - Response: api.APIErrorUnsupportedGrantType, + Response: api.ErrorUnsupportedGrantType, } return res.Send(ctx) } @@ -82,14 +82,14 @@ func HandleAccessTokenEndpoint(ctx *fiber.Ctx) error { if ok := mt.Restrictions.VerifyForAT(nil, ctx.IP(), mt.ID); !ok { return (&serverModel.Response{ Status: fiber.StatusForbidden, - Response: api.APIErrorUsageRestricted, + Response: api.ErrorUsageRestricted, }).Send(ctx) } log.Trace("Checked mytoken restrictions") if ok := mt.VerifyCapabilities(api.CapabilityAT); !ok { res := serverModel.Response{ Status: fiber.StatusForbidden, - Response: api.APIErrorInsufficientCapabilities, + Response: api.ErrorInsufficientCapabilities, } return res.Send(ctx) } @@ -113,7 +113,7 @@ func handleAccessTokenRefresh(mt *mytoken.Mytoken, req request.AccessTokenReques if !ok { return &serverModel.Response{ Status: fiber.StatusBadRequest, - Response: api.APIErrorUnknownIssuer, + Response: api.ErrorUnknownIssuer, } } @@ -125,7 +125,7 @@ func handleAccessTokenRefresh(mt *mytoken.Mytoken, req request.AccessTokenReques if len(possibleRestrictions) == 0 { return &serverModel.Response{ Status: fiber.StatusForbidden, - Response: api.APIErrorUsageRestricted, + Response: api.ErrorUsageRestricted, } } usedRestriction = &possibleRestrictions[0] diff --git a/internal/endpoints/token/mytoken/mytokenEndpoint.go b/internal/endpoints/token/mytoken/mytokenEndpoint.go index 0bd8525d..a1684a97 100644 --- a/internal/endpoints/token/mytoken/mytokenEndpoint.go +++ b/internal/endpoints/token/mytoken/mytokenEndpoint.go @@ -48,7 +48,7 @@ func HandleMytokenEndpoint(ctx *fiber.Ctx) error { } res := serverModel.Response{ Status: fiber.StatusBadRequest, - Response: api.APIErrorUnsupportedGrantType, + Response: api.ErrorUnsupportedGrantType, } return res.Send(ctx) } @@ -62,7 +62,7 @@ func handleOIDCFlow(ctx *fiber.Ctx) error { if !ok { return serverModel.Response{ Status: fiber.StatusBadRequest, - Response: api.APIErrorUnknownIssuer, + Response: api.ErrorUnknownIssuer, }.Send(ctx) } switch req.OIDCFlow { @@ -73,7 +73,7 @@ func handleOIDCFlow(ctx *fiber.Ctx) error { default: res := serverModel.Response{ Status: fiber.StatusBadRequest, - Response: api.APIErrorUnsupportedOIDCFlow, + Response: api.ErrorUnsupportedOIDCFlow, } return res.Send(ctx) } diff --git a/internal/endpoints/token/mytoken/polling/pollingEndpoint.go b/internal/endpoints/token/mytoken/polling/pollingEndpoint.go index 37582eaf..f183d8e0 100644 --- a/internal/endpoints/token/mytoken/polling/pollingEndpoint.go +++ b/internal/endpoints/token/mytoken/polling/pollingEndpoint.go @@ -34,21 +34,21 @@ func handlePollingCode(req response.PollingCodeRequest, networkData api.ClientMe log.WithField("polling_code", pollingCode).Debug("Polling code not known") return &model.Response{ Status: fiber.StatusUnauthorized, - Response: api.APIErrorBadTransferCode, + Response: api.ErrorBadTransferCode, } } if pollingCodeStatus.ConsentDeclined { log.WithField("polling_code", pollingCode).Debug("Consent declined") return &model.Response{ Status: fiber.StatusUnauthorized, - Response: api.APIErrorConsentDeclined, + Response: api.ErrorConsentDeclined, } } if pollingCodeStatus.Expired { log.WithField("polling_code", pollingCode).Debug("Polling code expired") return &model.Response{ Status: fiber.StatusUnauthorized, - Response: api.APIErrorTransferCodeExpired, + Response: api.ErrorTransferCodeExpired, } } token, err := transfercoderepo.PopTokenForTransferCode(nil, pollingCode, networkData) @@ -59,7 +59,7 @@ func handlePollingCode(req response.PollingCodeRequest, networkData api.ClientMe if token == "" { return &model.Response{ Status: fiber.StatusPreconditionRequired, - Response: api.APIErrorAuthorizationPending, + Response: api.ErrorAuthorizationPending, } } mt, err := mytoken.ParseJWT(token) diff --git a/internal/endpoints/tokeninfo/history.go b/internal/endpoints/tokeninfo/history.go index f56f2a29..b302504b 100644 --- a/internal/endpoints/tokeninfo/history.go +++ b/internal/endpoints/tokeninfo/history.go @@ -24,7 +24,7 @@ func handleTokenInfoHistory(mt *mytoken.Mytoken, clientMetadata *api.ClientMetaD if !mt.Capabilities.Has(api.CapabilityTokeninfoHistory) { return model.Response{ Status: fiber.StatusForbidden, - Response: api.APIErrorInsufficientCapabilities, + Response: api.ErrorInsufficientCapabilities, } } @@ -34,7 +34,7 @@ func handleTokenInfoHistory(mt *mytoken.Mytoken, clientMetadata *api.ClientMetaD if len(possibleRestrictions) == 0 { return model.Response{ Status: fiber.StatusForbidden, - Response: api.APIErrorUsageRestricted, + Response: api.ErrorUsageRestricted, } } usedRestriction = &possibleRestrictions[0] diff --git a/internal/endpoints/tokeninfo/introspect.go b/internal/endpoints/tokeninfo/introspect.go index ac26bec8..0b34b480 100644 --- a/internal/endpoints/tokeninfo/introspect.go +++ b/internal/endpoints/tokeninfo/introspect.go @@ -19,7 +19,7 @@ func handleTokenInfoIntrospect(mt *mytoken.Mytoken, clientMetadata *api.ClientMe if !mt.Capabilities.Has(api.CapabilityTokeninfoIntrospect) { return model.Response{ Status: fiber.StatusForbidden, - Response: api.APIErrorInsufficientCapabilities, + Response: api.ErrorInsufficientCapabilities, } } var usedToken mytoken.UsedMytoken diff --git a/internal/endpoints/tokeninfo/list.go b/internal/endpoints/tokeninfo/list.go index 6a4c48b9..970dc84b 100644 --- a/internal/endpoints/tokeninfo/list.go +++ b/internal/endpoints/tokeninfo/list.go @@ -24,7 +24,7 @@ func handleTokenInfoList(mt *mytoken.Mytoken, clientMetadata *api.ClientMetaData if !mt.Capabilities.Has(api.CapabilityListMT) { return model.Response{ Status: fiber.StatusForbidden, - Response: api.APIErrorInsufficientCapabilities, + Response: api.ErrorInsufficientCapabilities, } } @@ -34,7 +34,7 @@ func handleTokenInfoList(mt *mytoken.Mytoken, clientMetadata *api.ClientMetaData if len(possibleRestrictions) == 0 { return model.Response{ Status: fiber.StatusForbidden, - Response: api.APIErrorUsageRestricted, + Response: api.ErrorUsageRestricted, } } usedRestriction = &possibleRestrictions[0] diff --git a/internal/endpoints/tokeninfo/tree.go b/internal/endpoints/tokeninfo/tree.go index 1556dca9..b02909fc 100644 --- a/internal/endpoints/tokeninfo/tree.go +++ b/internal/endpoints/tokeninfo/tree.go @@ -24,7 +24,7 @@ func handleTokenInfoTree(mt *mytoken.Mytoken, clientMetadata *api.ClientMetaData if !mt.Capabilities.Has(api.CapabilityTokeninfoTree) { return model.Response{ Status: fiber.StatusForbidden, - Response: api.APIErrorInsufficientCapabilities, + Response: api.ErrorInsufficientCapabilities, } } @@ -34,7 +34,7 @@ func handleTokenInfoTree(mt *mytoken.Mytoken, clientMetadata *api.ClientMetaData if len(possibleRestrictions) == 0 { return model.Response{ Status: fiber.StatusForbidden, - Response: api.APIErrorUsageRestricted, + Response: api.ErrorUsageRestricted, } } usedRestriction = &possibleRestrictions[0] diff --git a/internal/model/response.go b/internal/model/response.go index 56faee62..7c9c36c9 100644 --- a/internal/model/response.go +++ b/internal/model/response.go @@ -46,4 +46,4 @@ func ErrorToBadRequestErrorResponse(err error) *Response { } // ResponseNYI is the server response when something is not yet implemented -var ResponseNYI = Response{Status: fiber.StatusNotImplemented, Response: api.APIErrorNYI} +var ResponseNYI = Response{Status: fiber.StatusNotImplemented, Response: api.ErrorNYI} diff --git a/internal/model/restrictionKey.go b/internal/model/restrictionKey.go new file mode 100644 index 00000000..92eb79d1 --- /dev/null +++ b/internal/model/restrictionKey.go @@ -0,0 +1,122 @@ +package model + +import ( + "encoding/json" + "fmt" + + "github.com/oidc-mytoken/api/v0" + yaml "gopkg.in/yaml.v3" +) + +// RestrictionKey is an enum like type for restriction keys +type RestrictionKey int + +// RestrictionKeys is a slice of RestrictionKey +type RestrictionKeys []RestrictionKey + +// AllRestrictionKeyStrings holds all defined RestrictionKey strings +var AllRestrictionKeyStrings = api.AllRestrictionKeys + +var AllRestrictionKeys RestrictionKeys + +func init() { + for i := 0; i < int(maxRestrictionKey); i++ { + AllRestrictionKeys = append(AllRestrictionKeys, RestrictionKey(i)) + } +} + +// RestrictionKeys +const ( // assert that these are in the same order as api.AllRestrictionKeys + RestrictionKeyNotBefore RestrictionKey = iota + RestrictionKeyExpiresAt + RestrictionKeyScope + RestrictionKeyAudiences + RestrictionKeyIPs + RestrictionKeyGeoIPAllow + RestrictionKeyGeoIPDisallow + RestrictionKeyUsagesAT + RestrictionKeyUsagesOther + maxRestrictionKey +) + +// NewRestrictionKey creates a new RestrictionKey from the grant type string +func NewRestrictionKey(s string) RestrictionKey { + for i, f := range AllRestrictionKeyStrings { + if f == s { + return RestrictionKey(i) + } + } + return -1 +} + +func (rk *RestrictionKey) String() string { + if *rk < 0 || int(*rk) >= len(AllRestrictionKeys) { + return "" + } + return AllRestrictionKeyStrings[*rk] +} + +// Valid checks that RestrictionKey is a defined grant type +func (rk *RestrictionKey) Valid() bool { + return *rk < maxRestrictionKey && *rk >= 0 +} + +// UnmarshalYAML implements the yaml.Unmarshaler interface +func (rk *RestrictionKey) UnmarshalYAML(value *yaml.Node) error { + s := value.Value + if s == "" { + return fmt.Errorf("empty value in unmarshal grant type") + } + *rk = NewRestrictionKey(s) + if !rk.Valid() { + return fmt.Errorf("value '%s' not valid for RestrictionKey", s) + } + return nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface +func (rk *RestrictionKey) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + *rk = NewRestrictionKey(s) + if !rk.Valid() { + return fmt.Errorf("value '%s' not valid for RestrictionKey", s) + } + return nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface +func (rk *RestrictionKey) UnmarshalText(data []byte) error { + s := string(data) + *rk = NewRestrictionKey(s) + if !rk.Valid() { + return fmt.Errorf("value '%s' not valid for RestrictionKey", s) + } + return nil +} + +// MarshalJSON implements the json.Marshaler interface +func (rk RestrictionKey) MarshalJSON() ([]byte, error) { + return json.Marshal(rk.String()) +} + +// Has checks if a a RestrictionKey is in a RestrictionKeys +func (rks RestrictionKeys) Has(rk RestrictionKey) bool { + for _, k := range rks { + if k == rk { + return true + } + } + return false +} + +func (rks RestrictionKeys) Disable(disable RestrictionKeys) (left RestrictionKeys) { + for _, r := range rks { + if !disable.Has(r) { + left = append(left, r) + } + } + return +} diff --git a/internal/oidc/authcode/authcode.go b/internal/oidc/authcode/authcode.go index 3d978d1a..3420c4a8 100644 --- a/internal/oidc/authcode/authcode.go +++ b/internal/oidc/authcode/authcode.go @@ -75,11 +75,13 @@ func GetAuthorizationURL(provider *config.ProviderConf, oState string, restricti func StartAuthCodeFlow(ctx *fiber.Ctx, oidcReq response.OIDCFlowRequest) *model.Response { log.Debug("Handle authcode") req := oidcReq.ToAuthCodeFlowRequest() + req.Restrictions.ReplaceThisIp(ctx.IP()) + req.Restrictions.ClearUnsupportedKeys() provider, ok := config.Get().ProviderByIssuer[req.Issuer] if !ok { return &model.Response{ Status: fiber.StatusBadRequest, - Response: api.APIErrorUnknownIssuer, + Response: api.ErrorUnknownIssuer, } } exp := req.Restrictions.GetExpires() @@ -90,7 +92,6 @@ func StartAuthCodeFlow(ctx *fiber.Ctx, oidcReq response.OIDCFlowRequest) *model. } } - req.Restrictions.ReplaceThisIp(ctx.IP()) oState, consentCode := state.CreateState(state.Info{Native: req.Native()}) authFlowInfoO := authcodeinforepo.AuthFlowInfoOut{ State: oState, @@ -132,7 +133,7 @@ func CodeExchange(oState *state.State, code string, networkData api.ClientMetaDa if errors.Is(err, sql.ErrNoRows) { return &model.Response{ Status: fiber.StatusBadRequest, - Response: api.APIErrorStateMismatch, + Response: api.ErrorStateMismatch, } } return model.ErrorToInternalServerErrorResponse(err) @@ -141,7 +142,7 @@ func CodeExchange(oState *state.State, code string, networkData api.ClientMetaDa if !ok { return &model.Response{ Status: fiber.StatusBadRequest, - Response: api.APIErrorUnknownIssuer, + Response: api.ErrorUnknownIssuer, } } oauth2Config := oauth2.Config{ @@ -168,7 +169,7 @@ func CodeExchange(oState *state.State, code string, networkData api.ClientMetaDa if token.RefreshToken == "" { return &model.Response{ Status: fiber.StatusInternalServerError, - Response: api.APIErrorNoRefreshToken, + Response: api.ErrorNoRefreshToken, } } scopes := authInfo.Restrictions.GetScopes() diff --git a/internal/server/errorHandler.go b/internal/server/errorHandler.go index b5bca0b2..0e59a267 100644 --- a/internal/server/errorHandler.go +++ b/internal/server/errorHandler.go @@ -53,7 +53,7 @@ func handleErrorHTML(ctx *fiber.Ctx, code int, msg string) error { func handleErrorJSON(ctx *fiber.Ctx, code int, msg string) error { return model.Response{ Status: code, - Response: api.APIError{ + Response: api.Error{ Error: fiberUtils.StatusMessage(code), ErrorDescription: msg, }, diff --git a/internal/server/server.go b/internal/server/server.go index b212d57e..926f25d0 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -75,7 +75,7 @@ func Init() { } return model.Response{ Status: fiber.StatusNotFound, - Response: api.APIError{ + Response: api.Error{ Error: "not_found", }, }.Send(ctx) diff --git a/shared/model/apiError.go b/shared/model/apiError.go index 927a1837..66305517 100644 --- a/shared/model/apiError.go +++ b/shared/model/apiError.go @@ -7,29 +7,29 @@ import ( "github.com/oidc-mytoken/api/v0" ) -// InternalServerError creates an APIError for internal server errors -func InternalServerError(errorDescription string) api.APIError { - return api.APIError{ - Error: api.ErrorInternal, +// InternalServerError creates an Error for internal server errors +func InternalServerError(errorDescription string) api.Error { + return api.Error{ + Error: api.ErrorStrInternal, ErrorDescription: errorDescription, } } -// OIDCError creates an APIError for oidc related errors -func OIDCError(oidcError, oidcErrorDescription string) api.APIError { +// OIDCError creates an Error for oidc related errors +func OIDCError(oidcError, oidcErrorDescription string) api.Error { err := oidcError if oidcErrorDescription != "" { err = fmt.Sprintf("%s: %s", oidcError, oidcErrorDescription) } - return api.APIError{ - Error: api.ErrorOIDC, + return api.Error{ + Error: api.ErrorStrOIDC, ErrorDescription: err, } } -// OIDCErrorFromBody creates an APIError for oidc related errors from the response of an oidc provider -func OIDCErrorFromBody(body []byte) (apiError api.APIError, ok bool) { - bodyError := api.APIError{} +// OIDCErrorFromBody creates an Error for oidc related errors from the response of an oidc provider +func OIDCErrorFromBody(body []byte) (apiError api.Error, ok bool) { + bodyError := api.Error{} if err := json.Unmarshal(body, &bodyError); err != nil { return } @@ -38,32 +38,32 @@ func OIDCErrorFromBody(body []byte) (apiError api.APIError, ok bool) { return } -// BadRequestError creates an APIError for bad request errors -func BadRequestError(errorDescription string) api.APIError { - return api.APIError{ - Error: api.ErrorInvalidRequest, +// BadRequestError creates an Error for bad request errors +func BadRequestError(errorDescription string) api.Error { + return api.Error{ + Error: api.ErrorStrInvalidRequest, ErrorDescription: errorDescription, } } -// InvalidTokenError creates an APIError for invalid token errors -func InvalidTokenError(errorDescription string) api.APIError { - return api.APIError{ - Error: api.ErrorInvalidToken, +// InvalidTokenError creates an Error for invalid token errors +func InvalidTokenError(errorDescription string) api.Error { + return api.Error{ + Error: api.ErrorStrInvalidToken, ErrorDescription: errorDescription, } } -// ErrorWithoutDescription creates an APIError from an error string -func ErrorWithoutDescription(err string) api.APIError { - return api.APIError{ +// ErrorWithoutDescription creates an Error from an error string +func ErrorWithoutDescription(err string) api.Error { + return api.Error{ Error: err, } } -// ErrorWithErrorDescription creates an APIError from an error string and golang error -func ErrorWithErrorDescription(e string, err error) api.APIError { - return api.APIError{ +// ErrorWithErrorDescription creates an Error from an error string and golang error +func ErrorWithErrorDescription(e string, err error) api.Error { + return api.Error{ Error: e, ErrorDescription: err.Error(), } diff --git a/shared/mytoken/mytokenHandler.go b/shared/mytoken/mytokenHandler.go index 675fb9d3..6a1af57c 100644 --- a/shared/mytoken/mytokenHandler.go +++ b/shared/mytoken/mytokenHandler.go @@ -51,14 +51,14 @@ func HandleMytokenFromTransferCode(ctx *fiber.Ctx) *model.Response { if !status.Found { errorRes = &model.Response{ Status: fiber.StatusUnauthorized, - Response: api.APIErrorBadTransferCode, + Response: api.ErrorBadTransferCode, } return fmt.Errorf("error_res") } if status.Expired { errorRes = &model.Response{ Status: fiber.StatusUnauthorized, - Response: api.APIErrorTransferCodeExpired, + Response: api.ErrorTransferCodeExpired, } return fmt.Errorf("error_res") } @@ -106,6 +106,8 @@ func HandleMytokenFromMytoken(ctx *fiber.Ctx) *model.Response { if err := json.Unmarshal(ctx.Body(), &req); err != nil { return model.ErrorToBadRequestErrorResponse(err) } + req.Restrictions.ReplaceThisIp(ctx.IP()) + req.Restrictions.ClearUnsupportedKeys() log.Trace("Parsed mytoken request") // GrantType already checked @@ -145,14 +147,14 @@ func HandleMytokenFromMytoken(ctx *fiber.Ctx) *model.Response { if ok := mt.VerifyCapabilities(api.CapabilityCreateMT); !ok { return &model.Response{ Status: fiber.StatusForbidden, - Response: api.APIErrorInsufficientCapabilities, + Response: api.ErrorInsufficientCapabilities, } } log.Trace("Checked mytoken capabilities") if ok := mt.Restrictions.VerifyForOther(nil, ctx.IP(), mt.ID); !ok { return &model.Response{ Status: fiber.StatusForbidden, - Response: api.APIErrorUsageRestricted, + Response: api.ErrorUsageRestricted, } } log.Trace("Checked mytoken restrictions") @@ -168,7 +170,6 @@ func HandleMytokenFromMytoken(ctx *fiber.Ctx) *model.Response { } log.Trace("Checked issuer") } - req.Restrictions.ReplaceThisIp(ctx.IP()) return handleMytokenFromMytoken(mt, req, ctxUtils.ClientMetaData(ctx), req.ResponseType) } @@ -278,7 +279,7 @@ func RevokeMytoken(tx *sqlx.Tx, id mtid.MTID, token token.Token, recursive bool, if !ok { return &model.Response{ Status: fiber.StatusBadRequest, - Response: api.APIErrorUnknownIssuer, + Response: api.ErrorUnknownIssuer, } } if err := db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { @@ -304,7 +305,7 @@ func RevokeMytoken(tx *sqlx.Tx, id mtid.MTID, token token.Token, recursive bool, return nil } if e := revoke.RefreshToken(provider, rt); e != nil { - apiError := e.Response.(api.APIError) + apiError := e.Response.(api.Error) return fmt.Errorf("%s: %s", apiError.Error, apiError.ErrorDescription) } return refreshtokenrepo.DeleteRefreshToken(tx, rtID) diff --git a/shared/mytoken/restrictions/restriction.go b/shared/mytoken/restrictions/restriction.go index 1b8b4a79..60969228 100644 --- a/shared/mytoken/restrictions/restriction.go +++ b/shared/mytoken/restrictions/restriction.go @@ -9,6 +9,7 @@ import ( "github.com/jinzhu/copier" "github.com/jmoiron/sqlx" "github.com/oidc-mytoken/server/internal/config" + "github.com/oidc-mytoken/server/internal/model" log "github.com/sirupsen/logrus" "github.com/oidc-mytoken/api/v0" @@ -23,6 +24,15 @@ import ( // Restrictions is a slice of Restriction type Restrictions []Restriction +var eRKs model.RestrictionKeys + +func disabledRestrictionKeys() model.RestrictionKeys { + if eRKs == nil { + eRKs = config.Get().Features.DisabledRestrictionKeys + } + return eRKs +} + // Restriction describes a token usage restriction type Restriction struct { NotBefore unixtime.UnixTime `json:"nbf,omitempty"` @@ -30,6 +40,40 @@ type Restriction struct { api.Restriction `json:",inline"` } +// ClearUnsupportedKeys sets default values for the keys that are not supported by this instance +func (r *Restrictions) ClearUnsupportedKeys() { + for i, rr := range *r { + if disabledRestrictionKeys().Has(model.RestrictionKeyNotBefore) { + rr.NotBefore = 0 + } + if disabledRestrictionKeys().Has(model.RestrictionKeyExpiresAt) { + rr.ExpiresAt = 0 + } + if disabledRestrictionKeys().Has(model.RestrictionKeyScope) { + rr.Scope = "" + } + if disabledRestrictionKeys().Has(model.RestrictionKeyAudiences) { + rr.Audiences = nil + } + if disabledRestrictionKeys().Has(model.RestrictionKeyIPs) { + rr.IPs = nil + } + if disabledRestrictionKeys().Has(model.RestrictionKeyGeoIPAllow) { + rr.GeoIPAllow = nil + } + if disabledRestrictionKeys().Has(model.RestrictionKeyGeoIPDisallow) { + rr.GeoIPDisallow = nil + } + if disabledRestrictionKeys().Has(model.RestrictionKeyUsagesAT) { + rr.UsagesAT = nil + } + if disabledRestrictionKeys().Has(model.RestrictionKeyUsagesOther) { + rr.UsagesOther = nil + } + (*r)[i] = rr + } +} + // hash returns the hash of this restriction func (r *Restriction) hash() ([]byte, error) { j, err := json.Marshal(r) @@ -39,16 +83,31 @@ func (r *Restriction) hash() ([]byte, error) { return hashUtils.SHA512(j), nil } +func (r *Restriction) verifyNbf(now unixtime.UnixTime) bool { + if disabledRestrictionKeys().Has(model.RestrictionKeyNotBefore) { + return true + } + return now >= r.NotBefore +} +func (r *Restriction) verifyExp(now unixtime.UnixTime) bool { + if disabledRestrictionKeys().Has(model.RestrictionKeyExpiresAt) { + return true + } + return r.ExpiresAt == 0 || + now <= r.ExpiresAt +} func (r *Restriction) verifyTimeBased() bool { log.Trace("Verifying time based") now := unixtime.Now() - return (now >= r.NotBefore) && (r.ExpiresAt == 0 || - now <= r.ExpiresAt) + return r.verifyNbf(now) && r.verifyExp(now) } func (r *Restriction) verifyIPBased(ip string) bool { return r.verifyIPs(ip) && r.verifyGeoIP(ip) } func (r *Restriction) verifyIPs(ip string) bool { + if disabledRestrictionKeys().Has(model.RestrictionKeyIPs) { + return true + } log.Trace("Verifying ips") return len(r.IPs) == 0 || utils.IPIsIn(ip, r.IPs) @@ -58,6 +117,9 @@ func (r *Restriction) verifyGeoIP(ip string) bool { return r.verifyGeoIPDisallow(ip) && r.verifyGeoIPAllow(ip) } func (r *Restriction) verifyGeoIPAllow(ip string) bool { + if disabledRestrictionKeys().Has(model.RestrictionKeyGeoIPAllow) { + return true + } log.Trace("Verifying ip geo location allow list") allow := r.GeoIPAllow if len(allow) == 0 { @@ -66,6 +128,9 @@ func (r *Restriction) verifyGeoIPAllow(ip string) bool { return utils.StringInSlice(geoip.CountryCode(ip), allow) } func (r *Restriction) verifyGeoIPDisallow(ip string) bool { + if disabledRestrictionKeys().Has(model.RestrictionKeyGeoIPDisallow) { + return true + } log.Trace("Verifying ip geo location disallow list") disallow := r.GeoIPDisallow if len(disallow) == 0 { @@ -81,6 +146,9 @@ func (r *Restriction) getATUsageCounts(tx *sqlx.Tx, myID mtid.MTID) (*int64, err return mytokenrepohelper.GetTokenUsagesAT(tx, myID, string(hash)) } func (r *Restriction) verifyATUsageCounts(tx *sqlx.Tx, myID mtid.MTID) bool { + if disabledRestrictionKeys().Has(model.RestrictionKeyUsagesAT) { + return true + } log.Trace("Verifying AT usage count") if r.UsagesAT == nil { return true @@ -112,6 +180,9 @@ func (r *Restriction) getOtherUsageCounts(tx *sqlx.Tx, myID mtid.MTID) (*int64, return mytokenrepohelper.GetTokenUsagesOther(tx, myID, string(hash)) } func (r *Restriction) verifyOtherUsageCounts(tx *sqlx.Tx, id mtid.MTID) bool { + if disabledRestrictionKeys().Has(model.RestrictionKeyUsagesOther) { + return true + } log.Trace("Verifying other usage count") if r.UsagesOther == nil { return true From 95e59712bc7c467f30241fac73f37db2e5672e23 Mon Sep 17 00:00:00 2001 From: zachmann Date: Fri, 11 Jun 2021 11:33:01 +0200 Subject: [PATCH 14/61] update version --- internal/model/version/version.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/model/version/version.go b/internal/model/version/version.go index 1c9fa822..b192fdaa 100644 --- a/internal/model/version/version.go +++ b/internal/model/version/version.go @@ -7,8 +7,8 @@ import ( // Version segments const ( MAJOR = 0 - MINOR = 2 - FIX = 1 + MINOR = 3 + FIX = 0 DEV = true ) From 1afbe2c9b879b33d81006a32cc96d572925de0a7 Mon Sep 17 00:00:00 2001 From: zachmann Date: Fri, 11 Jun 2021 14:15:59 +0200 Subject: [PATCH 15/61] use secret password files for db nodes in swarm --- docker/docker-swarm.yaml | 7 +++++-- docker/example-db.env | 6 +----- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docker/docker-swarm.yaml b/docker/docker-swarm.yaml index 2ebcdc1f..c3feac16 100644 --- a/docker/docker-swarm.yaml +++ b/docker/docker-swarm.yaml @@ -45,8 +45,8 @@ services: env_file: - db.env environment: - - MARIADB_ROOT_PASSWORD_FILE=/run/secrets/db_root_password - MARIADB_PASSWORD_FILE=/run/secrets/db_password + - MARIADB_ROOT_PASSWORD_FILE=/run/secrets/db_root_password - MARIADB_GALERA_MARIABACKUP_PASSWORD_FILE=/run/secrets/db_backup_password - MARIADB_REPLICATION_PASSWORD_FILE=/run/secrets/db_replication_password secrets: @@ -78,10 +78,14 @@ services: - db.env environment: - MARIADB_GALERA_CLUSTER_ADDRESS=gcomm://db-bootstrap,db_1,db_2,db_3 + - MARIADB_PASSWORD_FILE=/run/secrets/db_password + - MARIADB_ROOT_PASSWORD_FILE=/run/secrets/db_root_password - MARIADB_GALERA_MARIABACKUP_PASSWORD_FILE=/run/secrets/db_backup_password - MARIADB_REPLICATION_PASSWORD_FILE=/run/secrets/db_replication_password - WAIT_FOR_NODES=db-bootstrap:3306,db_1:3306,db_2:3306,db_3:3306 secrets: + - db_password + - db_root_password - db_backup_password - db_replication_password deploy: @@ -112,7 +116,6 @@ services: networks: - backend environment: - - DB_PASSWORD=password - DB_PASSWORD_FILE=/run/secrets/db_root_password - DB_DATABASE=mytoken - DB_NODES=db_1,db_2,db_3 diff --git a/docker/example-db.env b/docker/example-db.env index 05b55fc3..5ddb79c2 100644 --- a/docker/example-db.env +++ b/docker/example-db.env @@ -1,9 +1,5 @@ MARIADB_GALERA_CLUSTER_NAME=mytoken -MARIADB_ROOT_PASSWORD=password MARIADB_DATABASE=mytoken MARIADB_USER=mytoken -MARIADB_PASSWORD=mytoken MARIADB_GALERA_MARIABACKUP_USER=mariabackup_user -MARIADB_GALERA_MARIABACKUP_PASSWORD=mariabackup -MARIADB_REPLICATION_USER=replication_user -MARIADB_REPLICATION_PASSWORD=replication \ No newline at end of file +MARIADB_REPLICATION_USER=replication_user \ No newline at end of file From 490db53c22c4d60212e3ae46f276ca6a353e6e1e Mon Sep 17 00:00:00 2001 From: zachmann Date: Fri, 11 Jun 2021 16:35:17 +0200 Subject: [PATCH 16/61] fix db password not read from file --- docker/docker-swarm.yaml | 4 +--- internal/config/config.go | 9 +++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/docker/docker-swarm.yaml b/docker/docker-swarm.yaml index c3feac16..b1d6ec07 100644 --- a/docker/docker-swarm.yaml +++ b/docker/docker-swarm.yaml @@ -2,8 +2,6 @@ version: "3.9" services: mytoken: hostname: 'mytoken_{{.Task.Slot}}' -# depends_on: -# - db image: oidcmytoken/mytoken-server working_dir: /root/mytoken volumes: @@ -24,7 +22,7 @@ services: # placement: # max_replicas_per_node: 1 restart_policy: - condition: on-failure + condition: any delay: 120s window: 60s rollback_config: diff --git a/internal/config/config.go b/internal/config/config.go index 167d761c..d237d69d 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -28,9 +28,10 @@ var defaultConfig = Config{ Secure: true, }, DB: DBConf{ - Hosts: []string{"localhost"}, - User: "mytoken", - Password: "mytoken", + Hosts: []string{"localhost"}, + User: "mytoken", + // The default value for Password is "mytoken", but it is not set here, but returned in the GetPassword function, + // because the default value is only used if no password and no password file are provided DB: "mytoken", ReconnectInterval: 60, }, @@ -206,7 +207,7 @@ func (conf *DBConf) GetPassword() string { return conf.Password } if conf.PasswordFile == "" { - return "" + return "mytoken" } content, err := ioutil.ReadFile(conf.PasswordFile) if err != nil { From 31bb6b3de2942b3df1bab44bb70267547aaef145 Mon Sep 17 00:00:00 2001 From: zachmann Date: Fri, 11 Jun 2021 16:35:31 +0200 Subject: [PATCH 17/61] update mytoken lib dependency --- go.mod | 2 +- go.sum | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/go.mod b/go.mod index 3c85b8e6..ebde7e1c 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/jmoiron/sqlx v1.3.1 github.com/lestrrat-go/jwx v1.0.8 github.com/oidc-mytoken/api v0.0.0-20210610140005-29f166a942ec - github.com/oidc-mytoken/lib v0.2.1-0.20210604073533-566d9af1210e + github.com/oidc-mytoken/lib v0.2.1-0.20210611134913-0c477174fc1f github.com/satori/go.uuid v1.2.0 github.com/sirupsen/logrus v1.7.0 github.com/valyala/fasthttp v1.22.0 diff --git a/go.sum b/go.sum index 66561672..6bff4781 100644 --- a/go.sum +++ b/go.sum @@ -388,11 +388,10 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nishanths/exhaustive v0.0.0-20200525081945-8e46705b6132 h1:NjznefjSrral0MiR4KlB41io/d3OklvhcgQUdfZTqJE= github.com/nishanths/exhaustive v0.0.0-20200525081945-8e46705b6132/go.mod h1:wBEpHwM2OdmeNpdCvRPUlkEbBuaFmcK4Wv8Q7FuGW3c= -github.com/oidc-mytoken/api v0.0.0-20210604072940-156be40bab89/go.mod h1:S8t1XA42EFAgc3vUfis0g1LPGA4TXH0nfDynvgo6cwk= github.com/oidc-mytoken/api v0.0.0-20210610140005-29f166a942ec h1:DGLeZJnsVWq6y81d6AH9mo+J5dQO47VFH9K2bKYgAjk= github.com/oidc-mytoken/api v0.0.0-20210610140005-29f166a942ec/go.mod h1:S8t1XA42EFAgc3vUfis0g1LPGA4TXH0nfDynvgo6cwk= -github.com/oidc-mytoken/lib v0.2.1-0.20210604073533-566d9af1210e h1:UHswGI8MNr4dUh98rNOiNfNQHUK44va0d6wQwcFGa2c= -github.com/oidc-mytoken/lib v0.2.1-0.20210604073533-566d9af1210e/go.mod h1:vjpAk3Fn5Ow0MJMfy5lmLox29zQ89rhTNuvGjf/ql54= +github.com/oidc-mytoken/lib v0.2.1-0.20210611134913-0c477174fc1f h1:EqbUYQ45C2iTLHZ0mqtAK9+SxMSemj9gaAv9/csN9D0= +github.com/oidc-mytoken/lib v0.2.1-0.20210611134913-0c477174fc1f/go.mod h1:kwotzLmyV5WEsvU50JoF8Q5+rjLFhjQKAgBGoEw7FRE= github.com/oidc-mytoken/server v0.2.0/go.mod h1:6uFm+Za9NMK3gq4OOIeX3gs3T6leluVIWsGiM1zlQbA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= From e0457c2f94fc04b433c2716bb976d3096fd99eeb Mon Sep 17 00:00:00 2001 From: zachmann Date: Fri, 11 Jun 2021 18:01:57 +0200 Subject: [PATCH 18/61] add comments --- internal/config/config.go | 2 ++ internal/db/cluster/cluster.go | 5 +++++ internal/db/dbmigrate/migrate.go | 3 +++ .../dbrepo/authcodeinforepo/authcodeInfo.go | 1 + .../authcodeinforepo/state/consentcode.go | 4 ++++ .../db/dbrepo/authcodeinforepo/state/state.go | 5 +++++ .../authcodeinforepo/state/stateinfo.go | 5 +++++ internal/db/dbrepo/eventrepo/eventHistory.go | 3 +++ internal/db/dbrepo/mytokenrepo/mytoken.go | 2 ++ .../mytokenrepo/mytokenrepohelper/helpers.go | 1 + internal/db/dbrepo/mytokenrepo/tree/tree.go | 13 ++++++++---- .../dbrepo/refreshtokenrepo/refreshtoken.go | 1 + internal/db/dbrepo/versionrepo/versionrepo.go | 13 +++++++++++- internal/endpoints/consent/pkg/capability.go | 5 +++++ internal/endpoints/consent/pkg/restriction.go | 6 ++++++ .../token/mytoken/pkg/oidcFlowRequest.go | 1 + .../tokeninfo/pkg/tokenIntrospectResponse.go | 1 + .../tokeninfo/pkg/tokeninfoHistoryResponse.go | 2 ++ .../tokeninfo/pkg/tokeninfoListResponse.go | 2 ++ .../tokeninfo/pkg/tokeninfoRequest.go | 1 + .../tokeninfo/pkg/tokeninfoTreeResponse.go | 2 ++ internal/endpoints/tokeninfo/tokeninfo.go | 1 + internal/model/restrictionKey.go | 2 ++ internal/oidc/authcode/authcode.go | 1 + .../singleasciiencode/singleasciiencode.go | 9 ++++++++ shared/mytoken/event/event.go | 1 + shared/mytoken/pkg/mtid/mtid.go | 5 +++++ shared/mytoken/pkg/usedMytoken.go | 1 + .../mytoken/restrictions/usedRestriction.go | 2 ++ shared/mytoken/rotation/rotation.go | 1 + shared/utils/cryptUtils/cryptUtils.go | 21 +++++++++++++++---- shared/utils/jwtutils/jwtutils.go | 4 +++- shared/utils/unixtime/unixtime.go | 6 ++++++ 33 files changed, 122 insertions(+), 10 deletions(-) diff --git a/internal/config/config.go b/internal/config/config.go index d237d69d..2dc1a77f 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -152,6 +152,7 @@ type pollingConf struct { PollingInterval int64 `yaml:"polling_interval"` } +// DBConf is type for holding configuration for a db type DBConf struct { Hosts []string `yaml:"hosts"` User string `yaml:"user"` @@ -194,6 +195,7 @@ type ProviderConf struct { AudienceRequestParameter string `yaml:"audience_request_parameter"` } +// ServiceOperatorConf is type holding the configuration for the service operator of this mytoken instance type ServiceOperatorConf struct { Name string `yaml:"name"` Homepage string `yaml:"homepage"` diff --git a/internal/db/cluster/cluster.go b/internal/db/cluster/cluster.go index c2200002..03b61039 100644 --- a/internal/db/cluster/cluster.go +++ b/internal/db/cluster/cluster.go @@ -14,6 +14,7 @@ import ( _ "github.com/go-sql-driver/mysql" ) +// NewFromConfig creates a new Cluster from the passed config.DBConf func NewFromConfig(conf config.DBConf) *Cluster { c := newCluster(len(conf.Hosts)) c.conf = &conf @@ -32,6 +33,7 @@ func newCluster(size int) *Cluster { return c } +// Cluster is a type for holding a db cluster type Cluster struct { active chan *node down chan *node @@ -54,6 +56,7 @@ func (n *node) close() { } } +// AddNodes adds the nodes specified for this Cluster to the cluster func (c *Cluster) AddNodes() { for _, host := range c.conf.Hosts { if err := c.AddNode(host); err != nil { @@ -62,6 +65,7 @@ func (c *Cluster) AddNodes() { } } +// AddNode adds the passed host a a db node to the cluster func (c *Cluster) AddNode(host string) error { log.WithField("host", host).Debug("Adding node to db cluster") dsn := fmt.Sprintf("%s:%s@%s(%s)/%s?parseTime=true", c.conf.User, c.conf.GetPassword(), "tcp", host, c.conf.DB) @@ -125,6 +129,7 @@ func (c *Cluster) checkNodesDown() bool { return false } +// Close closes the cluster func (c *Cluster) Close() { c.stop <- struct{}{} for { diff --git a/internal/db/dbmigrate/migrate.go b/internal/db/dbmigrate/migrate.go index dee6ad3e..95b36b3e 100644 --- a/internal/db/dbmigrate/migrate.go +++ b/internal/db/dbmigrate/migrate.go @@ -1,12 +1,15 @@ package dbmigrate +// Commands is a type for holding sql commands that should run before and after a version update type Commands struct { Before []string `yaml:"before"` After []string `yaml:"after"` } +// VersionCommands is type holding the Commands that are related to a mytoken version type VersionCommands map[string]Commands +// Migrate holds the VersionCommands for mytoken. These commands are used to migrate the database between mytoken versions. var Migrate = VersionCommands{ "0.2.0": {Before: v0_2_0_Before}, } diff --git a/internal/db/dbrepo/authcodeinforepo/authcodeInfo.go b/internal/db/dbrepo/authcodeinforepo/authcodeInfo.go index 87e95ca2..caddf1d9 100644 --- a/internal/db/dbrepo/authcodeinforepo/authcodeInfo.go +++ b/internal/db/dbrepo/authcodeinforepo/authcodeInfo.go @@ -99,6 +99,7 @@ func DeleteAuthFlowInfoByState(tx *sqlx.Tx, state *state.State) error { }) } +// UpdateTokenInfoByState updates the stored AuthFlowInfo for the given state func UpdateTokenInfoByState(tx *sqlx.Tx, state *state.State, r restrictions.Restrictions, c, sc api.Capabilities) error { return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { _, err := tx.Exec(`UPDATE AuthInfo SET restrictions=?, capabilities=?, subtoken_capabilities=? WHERE state_h=?`, r, c, sc, state) diff --git a/internal/db/dbrepo/authcodeinforepo/state/consentcode.go b/internal/db/dbrepo/authcodeinforepo/state/consentcode.go index 95ba2320..098443ee 100644 --- a/internal/db/dbrepo/authcodeinforepo/state/consentcode.go +++ b/internal/db/dbrepo/authcodeinforepo/state/consentcode.go @@ -8,6 +8,7 @@ import ( const stateLen = 16 const consentCodeLen = 8 +// NewConsentCode creates a new ConsentCode func NewConsentCode(info Info) *ConsentCode { return &ConsentCode{ r: utils.RandASCIIString(consentCodeLen), @@ -15,6 +16,7 @@ func NewConsentCode(info Info) *ConsentCode { } } +// ParseConsentCode parses a string into a ConsentCode func ParseConsentCode(cc string) *ConsentCode { return &ConsentCode{ r: cc[:len(cc)-infoAsciiLen], @@ -23,6 +25,7 @@ func ParseConsentCode(cc string) *ConsentCode { } } +// ConsentCode is type for the code used for giving consent to mytoken type ConsentCode struct { r string encodedInfo string @@ -37,6 +40,7 @@ func (c *ConsentCode) String() string { return c.public } +// GetState returns the state linked to a ConsentCode func (c *ConsentCode) GetState() string { if c.state == "" { c.state = hashUtils.HMACSHA512Str([]byte("state"), []byte(c.r))[:stateLen] + c.encodedInfo diff --git a/internal/db/dbrepo/authcodeinforepo/state/state.go b/internal/db/dbrepo/authcodeinforepo/state/state.go index 3937493e..b442e12d 100644 --- a/internal/db/dbrepo/authcodeinforepo/state/state.go +++ b/internal/db/dbrepo/authcodeinforepo/state/state.go @@ -11,18 +11,21 @@ import ( "github.com/oidc-mytoken/server/internal/utils/hashUtils" ) +// State is a type for the oidc state type State struct { state string hash string pollingCode string } +// NewState creates a new State from a state string func NewState(state string) *State { return &State{ state: state, } } +// Hash returns the hash for this State func (s *State) Hash() string { if s.hash == "" { s.hash = hashUtils.SHA512Str([]byte(s.state)) @@ -30,6 +33,7 @@ func (s *State) Hash() string { return s.hash } +// PollingCode returns the polling code for this State func (s *State) PollingCode() string { if s.pollingCode == "" { s.pollingCode = hashUtils.HMACSHA512Str([]byte("polling_code"), []byte(s.state))[:config.Get().Features.Polling.Len] @@ -38,6 +42,7 @@ func (s *State) PollingCode() string { return s.pollingCode } +// State returns the state string for this State func (s State) State() string { return s.state } diff --git a/internal/db/dbrepo/authcodeinforepo/state/stateinfo.go b/internal/db/dbrepo/authcodeinforepo/state/stateinfo.go index 253b690a..2ada2c65 100644 --- a/internal/db/dbrepo/authcodeinforepo/state/stateinfo.go +++ b/internal/db/dbrepo/authcodeinforepo/state/stateinfo.go @@ -5,6 +5,7 @@ import ( pkgModel "github.com/oidc-mytoken/server/shared/model" ) +// Info is a type for holding the information encoded in a State type Info struct { Native bool ResponseType pkgModel.ResponseType @@ -12,6 +13,7 @@ type Info struct { const infoAsciiLen = 2 +// Encode encodes the Info into a string func (i Info) Encode() string { fe := singleasciiencode.NewFlagEncoder() fe.Set("native", i.Native) @@ -20,6 +22,7 @@ func (i Info) Encode() string { return string([]byte{flags, responseType}) } +// Decode decodes the Info from a string func (i *Info) Decode(s string) { length := len(s) if length < infoAsciiLen { @@ -31,12 +34,14 @@ func (i *Info) Decode(s string) { i.Native, _ = flags.Get("native") } +// CreateState creates a new State and ConsentCode from the passed Info func CreateState(info Info) (*State, *ConsentCode) { consentCode := NewConsentCode(info) s := consentCode.GetState() return NewState(s), consentCode } +// Parse parses a State and returns the encoded Info func (s *State) Parse() (info Info) { info.Decode(s.State()) return diff --git a/internal/db/dbrepo/eventrepo/eventHistory.go b/internal/db/dbrepo/eventrepo/eventHistory.go index 155819e6..76b6ceb0 100644 --- a/internal/db/dbrepo/eventrepo/eventHistory.go +++ b/internal/db/dbrepo/eventrepo/eventHistory.go @@ -9,14 +9,17 @@ import ( "github.com/oidc-mytoken/server/shared/utils/unixtime" ) +// EventHistory is type for multiple EventEntry type EventHistory []EventEntry +// EventEntry represents a mytoken event type EventEntry struct { api.EventEntry `json:",inline"` MTID mtid.MTID `db:"MT_id" json:"-"` Time unixtime.UnixTime `db:"time" json:"time"` } +// GetEventHistory returns the stored EventHistory for a mytoken func GetEventHistory(tx *sqlx.Tx, id mtid.MTID) (history EventHistory, err error) { err = db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { return tx.Select(&history, `SELECT MT_id, event, time, comment, ip, user_agent FROM EventHistory WHERE MT_id=?`, id) diff --git a/internal/db/dbrepo/mytokenrepo/mytoken.go b/internal/db/dbrepo/mytokenrepo/mytoken.go index 098eb81f..31eeda5b 100644 --- a/internal/db/dbrepo/mytokenrepo/mytoken.go +++ b/internal/db/dbrepo/mytokenrepo/mytoken.go @@ -35,6 +35,7 @@ type MytokenEntry struct { networkData api.ClientMetaData } +// InitRefreshToken links a refresh token to this MytokenEntry func (ste *MytokenEntry) InitRefreshToken(rt string) error { ste.refreshToken = rt ste.encryptionKey = cryptUtils.RandomBytes(32) @@ -55,6 +56,7 @@ func (ste *MytokenEntry) InitRefreshToken(rt string) error { return nil } +// SetRefreshToken updates the refresh token for this MytokenEntry func (ste *MytokenEntry) SetRefreshToken(rtID uint64, key []byte) error { ste.encryptionKey = key jwt, err := ste.Token.ToJWT() diff --git a/internal/db/dbrepo/mytokenrepo/mytokenrepohelper/helpers.go b/internal/db/dbrepo/mytokenrepo/mytokenrepohelper/helpers.go index e1028ef3..1eda3bb0 100644 --- a/internal/db/dbrepo/mytokenrepo/mytokenrepohelper/helpers.go +++ b/internal/db/dbrepo/mytokenrepo/mytokenrepohelper/helpers.go @@ -12,6 +12,7 @@ import ( "github.com/oidc-mytoken/server/shared/mytoken/rotation" ) +// ParseError parses the passed error for a sql.ErrNoRows func ParseError(err error) (bool, error) { if err != nil { if errors.Is(err, sql.ErrNoRows) { diff --git a/internal/db/dbrepo/mytokenrepo/tree/tree.go b/internal/db/dbrepo/mytokenrepo/tree/tree.go index 8f637470..68e5a024 100644 --- a/internal/db/dbrepo/mytokenrepo/tree/tree.go +++ b/internal/db/dbrepo/mytokenrepo/tree/tree.go @@ -34,25 +34,29 @@ func (ste *MytokenEntry) Root() bool { return !ste.RootID.HashValid() } -func GetUserID(tx *sqlx.Tx, tokenID mtid.MTID) (uid int64, err error) { +// getUserID returns the user id linked to a mytoken +func getUserID(tx *sqlx.Tx, tokenID mtid.MTID) (uid int64, err error) { err = db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { return tx.Get(&uid, `SELECT user_id FROM MTokens WHERE id=? ORDER BY name`, tokenID) }) return } +// AllTokens returns information about all mytokens for the user linked to the passed mytoken func AllTokens(tx *sqlx.Tx, tokenID mtid.MTID) (trees []MytokenEntryTree, err error) { err = db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { - uid, e := GetUserID(tx, tokenID) + uid, e := getUserID(tx, tokenID) if e != nil { return e } - trees, err = AllTokensForUser(tx, uid) + trees, err = allTokensForUser(tx, uid) return err }) return } -func AllTokensForUser(tx *sqlx.Tx, uid int64) ([]MytokenEntryTree, error) { + +// allTokensForUser returns information about all mytoken for the passed user +func allTokensForUser(tx *sqlx.Tx, uid int64) ([]MytokenEntryTree, error) { var tokens []MytokenEntry if err := db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { return tx.Select(&tokens, `SELECT id, parent_id, root_id, name, created, ip_created AS ip FROM MTokens WHERE user_id=?`, uid) @@ -70,6 +74,7 @@ func subtokens(tx *sqlx.Tx, rootID mtid.MTID) ([]MytokenEntry, error) { return tokens, err } +// TokenSubTree returns information about all subtokens for the passed mytoken func TokenSubTree(tx *sqlx.Tx, tokenID mtid.MTID) (MytokenEntryTree, error) { var tokens []MytokenEntry var root MytokenEntry diff --git a/internal/db/dbrepo/refreshtokenrepo/refreshtoken.go b/internal/db/dbrepo/refreshtokenrepo/refreshtoken.go index 4d39debe..fcd27b31 100644 --- a/internal/db/dbrepo/refreshtokenrepo/refreshtoken.go +++ b/internal/db/dbrepo/refreshtokenrepo/refreshtoken.go @@ -27,6 +27,7 @@ func UpdateRefreshToken(tx *sqlx.Tx, tokenID mtid.MTID, newRT, jwt string) error }) } +// GetEncryptionKey returns the encryption key and its id for a mytoken func GetEncryptionKey(tx *sqlx.Tx, tokenID mtid.MTID, jwt string) ([]byte, uint64, error) { var key []byte var rtID uint64 diff --git a/internal/db/dbrepo/versionrepo/versionrepo.go b/internal/db/dbrepo/versionrepo/versionrepo.go index 98b03a05..002b39bb 100644 --- a/internal/db/dbrepo/versionrepo/versionrepo.go +++ b/internal/db/dbrepo/versionrepo/versionrepo.go @@ -13,6 +13,7 @@ import ( "golang.org/x/mod/semver" ) +// SetVersionBefore sets that the before db migration commands for the passed version were executed func SetVersionBefore(tx *sqlx.Tx, version string) error { return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { _, err := tx.Exec(`INSERT INTO version (version, bef) VALUES(?, current_timestamp()) ON DUPLICATE KEY UPDATE bef=current_timestamp()`, version) @@ -20,6 +21,7 @@ func SetVersionBefore(tx *sqlx.Tx, version string) error { }) } +// SetVersionAfter sets that the after db migration commands for the passed version were executed func SetVersionAfter(tx *sqlx.Tx, version string) error { return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { _, err := tx.Exec(`INSERT INTO version (version, aft) VALUES(?, current_timestamp()) ON DUPLICATE KEY UPDATE aft=current_timestamp()`, version) @@ -27,21 +29,29 @@ func SetVersionAfter(tx *sqlx.Tx, version string) error { }) } +// UpdateTimes is a type for checking if the db migration commands for different mytoken version have been executed type UpdateTimes struct { Version string Before mysql.NullTime `db:"bef"` After mysql.NullTime `db:"aft"` } +// DBVersionState describes the version state of the db type DBVersionState []UpdateTimes -func (state DBVersionState) Len() int { return len(state) } +// Len returns the len of DBVersionState +func (state DBVersionState) Len() int { return len(state) } + +// Swap swaps to elements of DBVersionState func (state DBVersionState) Swap(i, j int) { state[i], state[j] = state[j], state[i] } + +// Less checks if a version is less than another func (state DBVersionState) Less(i, j int) bool { a, b := state[i].Version, state[j].Version return semver.Compare(a, b) < 0 } +// Sort sorts this DBVersionState by the version func (state DBVersionState) Sort() { sort.Sort(state) } @@ -74,6 +84,7 @@ func (state DBVersionState) dBHasVersion(v string, cmds dbmigrate.Commands) bool return ok } +// GetVersionState returns the DBVersionState func GetVersionState(tx *sqlx.Tx) (state DBVersionState, err error) { err = db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { return tx.Select(&state, `SELECT version, bef, aft FROM version`) diff --git a/internal/endpoints/consent/pkg/capability.go b/internal/endpoints/consent/pkg/capability.go index 39b01221..f7b454fa 100644 --- a/internal/endpoints/consent/pkg/capability.go +++ b/internal/endpoints/consent/pkg/capability.go @@ -5,11 +5,13 @@ import ( "github.com/oidc-mytoken/server/shared/utils" ) +// WebCapability is type for representing api.Capability in the consent screen type WebCapability struct { api.Capability intClass *int } +// WebCapabilities creates a slice of WebCapability from api.Capabilities func WebCapabilities(cc api.Capabilities) (wc []WebCapability) { for _, c := range cc { wc = append(wc, WebCapability{c, nil}) @@ -57,10 +59,12 @@ func (c WebCapability) getDangerLevel() int { return c.getIntClass() } +// ColorClass returns the html class for coloring this Capability func (c WebCapability) ColorClass() string { return textColorByDanger(c.getDangerLevel()) } +// CapabilityLevel returns a string describing the power of this capability func (c WebCapability) CapabilityLevel() string { intClass := c.getIntClass() switch intClass { @@ -74,6 +78,7 @@ func (c WebCapability) CapabilityLevel() string { return "" } +// IsCreateMT checks if this WebCapability is api.CapabilityCreateMT func (c WebCapability) IsCreateMT() bool { return c.Name == api.CapabilityCreateMT.Name } diff --git a/internal/endpoints/consent/pkg/restriction.go b/internal/endpoints/consent/pkg/restriction.go index fd39604b..e07991c5 100644 --- a/internal/endpoints/consent/pkg/restriction.go +++ b/internal/endpoints/consent/pkg/restriction.go @@ -9,6 +9,7 @@ import ( "github.com/oidc-mytoken/server/shared/utils/unixtime" ) +// WebRestrictions a type for representing restrictions.Restrictions in the consent screen type WebRestrictions struct { restrictions.Restrictions timeClass *int @@ -18,6 +19,7 @@ type WebRestrictions struct { usagesClass *bool } +// Text returns a textual (json) representation of this WebRestrictions func (r WebRestrictions) Text() string { data, _ := json.Marshal(r.Restrictions) fmt.Println(string(data)) @@ -110,6 +112,7 @@ func (r WebRestrictions) getUsageClass() bool { return u } +// TimeColorClass returns the html class for coloring the time dimension func (r WebRestrictions) TimeColorClass() string { intClass := r.getTimeClass() switch intClass { @@ -124,6 +127,7 @@ func (r WebRestrictions) TimeColorClass() string { } } +// TimeDescription returns a string describing the state of the time dimension func (r WebRestrictions) TimeDescription() string { intClass := r.getTimeClass() switch intClass { @@ -138,6 +142,7 @@ func (r WebRestrictions) TimeDescription() string { } } +// ScopeColorClass returns the html class for coloring the scope dimension func (r WebRestrictions) ScopeColorClass() string { if r.getScopeClass() { return "text-success" @@ -145,6 +150,7 @@ func (r WebRestrictions) ScopeColorClass() string { return "text-warning" } +// ScopeDescription returns a string describing the state of the scope dimension func (r WebRestrictions) ScopeDescription() string { if r.getScopeClass() { return "This token has restrictions for scopes." diff --git a/internal/endpoints/token/mytoken/pkg/oidcFlowRequest.go b/internal/endpoints/token/mytoken/pkg/oidcFlowRequest.go index 21f0b932..cd714953 100644 --- a/internal/endpoints/token/mytoken/pkg/oidcFlowRequest.go +++ b/internal/endpoints/token/mytoken/pkg/oidcFlowRequest.go @@ -65,6 +65,7 @@ func (r *OIDCFlowRequest) UnmarshalJSON(data []byte) error { return nil } +// ToAuthCodeFlowRequest creates a AuthCodeFlowRequest from the OIDCFlowRequest func (r OIDCFlowRequest) ToAuthCodeFlowRequest() AuthCodeFlowRequest { return AuthCodeFlowRequest{ OIDCFlowRequest: r, diff --git a/internal/endpoints/tokeninfo/pkg/tokenIntrospectResponse.go b/internal/endpoints/tokeninfo/pkg/tokenIntrospectResponse.go index 56197022..1e7a8ae5 100644 --- a/internal/endpoints/tokeninfo/pkg/tokenIntrospectResponse.go +++ b/internal/endpoints/tokeninfo/pkg/tokenIntrospectResponse.go @@ -5,6 +5,7 @@ import ( mytoken "github.com/oidc-mytoken/server/shared/mytoken/pkg" ) +// TokeninfoIntrospectResponse is type for responses to tokeninfo introspect requests type TokeninfoIntrospectResponse struct { api.TokeninfoIntrospectResponse `json:",inline"` Token mytoken.UsedMytoken `json:"token"` diff --git a/internal/endpoints/tokeninfo/pkg/tokeninfoHistoryResponse.go b/internal/endpoints/tokeninfo/pkg/tokeninfoHistoryResponse.go index 4633f27f..442bea29 100644 --- a/internal/endpoints/tokeninfo/pkg/tokeninfoHistoryResponse.go +++ b/internal/endpoints/tokeninfo/pkg/tokeninfoHistoryResponse.go @@ -4,11 +4,13 @@ import ( "github.com/oidc-mytoken/server/internal/db/dbrepo/eventrepo" ) +// TokeninfoHistoryResponse is type for responses to tokeninfo history requests type TokeninfoHistoryResponse struct { // un update check api.TokeninfoHistoryResponse EventHistory eventrepo.EventHistory `json:"events"` } +// NewTokeninfoHistoryResponse creates a new TokeninfoHistoryResponse func NewTokeninfoHistoryResponse(h eventrepo.EventHistory) TokeninfoHistoryResponse { return TokeninfoHistoryResponse{EventHistory: h} } diff --git a/internal/endpoints/tokeninfo/pkg/tokeninfoListResponse.go b/internal/endpoints/tokeninfo/pkg/tokeninfoListResponse.go index ba3de51a..b30ee4eb 100644 --- a/internal/endpoints/tokeninfo/pkg/tokeninfoListResponse.go +++ b/internal/endpoints/tokeninfo/pkg/tokeninfoListResponse.go @@ -4,11 +4,13 @@ import ( "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/tree" ) +// TokeninfoListResponse is type for responses to tokeninfo list requests type TokeninfoListResponse struct { // un update check api.TokeninfoListResponse Tokens []tree.MytokenEntryTree `json:"mytokens"` } +// NewTokeninfoListResponse creates a new TokeninfoListResponse func NewTokeninfoListResponse(l []tree.MytokenEntryTree) TokeninfoListResponse { return TokeninfoListResponse{Tokens: l} } diff --git a/internal/endpoints/tokeninfo/pkg/tokeninfoRequest.go b/internal/endpoints/tokeninfo/pkg/tokeninfoRequest.go index 8bb4603f..a4b040a9 100644 --- a/internal/endpoints/tokeninfo/pkg/tokeninfoRequest.go +++ b/internal/endpoints/tokeninfo/pkg/tokeninfoRequest.go @@ -6,6 +6,7 @@ import ( "github.com/oidc-mytoken/server/shared/mytoken/token" ) +// TokenInfoRequest is a type for holding a request to the tokeninfo endpoint type TokenInfoRequest struct { api.TokenInfoRequest `json:",inline"` Action model.TokeninfoAction `json:"action"` diff --git a/internal/endpoints/tokeninfo/pkg/tokeninfoTreeResponse.go b/internal/endpoints/tokeninfo/pkg/tokeninfoTreeResponse.go index 61a892d2..eff9b820 100644 --- a/internal/endpoints/tokeninfo/pkg/tokeninfoTreeResponse.go +++ b/internal/endpoints/tokeninfo/pkg/tokeninfoTreeResponse.go @@ -4,11 +4,13 @@ import ( "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/tree" ) +// TokeninfoTreeResponse is type for responses to tokeninfo tree requests type TokeninfoTreeResponse struct { // un update check api.TokeninforTeeResponse Tokens tree.MytokenEntryTree `json:"mytokens"` } +// NewTokeninfoTreeResponse creates a new TokeninfoTreeResponse func NewTokeninfoTreeResponse(t tree.MytokenEntryTree) TokeninfoTreeResponse { return TokeninfoTreeResponse{Tokens: t} } diff --git a/internal/endpoints/tokeninfo/tokeninfo.go b/internal/endpoints/tokeninfo/tokeninfo.go index 6c3f36ac..aa8d552b 100644 --- a/internal/endpoints/tokeninfo/tokeninfo.go +++ b/internal/endpoints/tokeninfo/tokeninfo.go @@ -14,6 +14,7 @@ import ( mytoken "github.com/oidc-mytoken/server/shared/mytoken/pkg" ) +// HandleTokenInfo handles requests to the tokeninfo endpoint func HandleTokenInfo(ctx *fiber.Ctx) error { var req pkg.TokenInfoRequest if err := json.Unmarshal(ctx.Body(), &req); err != nil { diff --git a/internal/model/restrictionKey.go b/internal/model/restrictionKey.go index 92eb79d1..f66f101a 100644 --- a/internal/model/restrictionKey.go +++ b/internal/model/restrictionKey.go @@ -17,6 +17,7 @@ type RestrictionKeys []RestrictionKey // AllRestrictionKeyStrings holds all defined RestrictionKey strings var AllRestrictionKeyStrings = api.AllRestrictionKeys +// AllRestrictionKeys holds all defined RestrictionKeys var AllRestrictionKeys RestrictionKeys func init() { @@ -112,6 +113,7 @@ func (rks RestrictionKeys) Has(rk RestrictionKey) bool { return false } +// Disable subtracts the passed RestrictionKeys from this RestrictionKeys and returns the left RestrictionKeys func (rks RestrictionKeys) Disable(disable RestrictionKeys) (left RestrictionKeys) { for _, r := range rks { if !disable.Has(r) { diff --git a/internal/oidc/authcode/authcode.go b/internal/oidc/authcode/authcode.go index 3420c4a8..95f419b1 100644 --- a/internal/oidc/authcode/authcode.go +++ b/internal/oidc/authcode/authcode.go @@ -44,6 +44,7 @@ func Init() { consentEndpoint = utils.CombineURLPath(config.Get().IssuerURL, generalPaths.ConsentEndpoint) } +// GetAuthorizationURL creates a authorization url func GetAuthorizationURL(provider *config.ProviderConf, oState string, restrictions restrictions.Restrictions) string { log.Debug("Generating authorization url") scopes := restrictions.GetScopes() diff --git a/internal/utils/singleasciiencode/singleasciiencode.go b/internal/utils/singleasciiencode/singleasciiencode.go index ae6baca8..ac528068 100644 --- a/internal/utils/singleasciiencode/singleasciiencode.go +++ b/internal/utils/singleasciiencode/singleasciiencode.go @@ -8,16 +8,19 @@ import ( const maxFlags = 6 +// NewFlagEncoder creates a new FlagEncoder func NewFlagEncoder() *FlagEncoder { return &FlagEncoder{} } +// FlagEncoder is type for encoding multiple binary (bool) flags into a single character type FlagEncoder struct { names [maxFlags]string f [maxFlags]bool nextIndex int } +// Set sets a name value pair that should be encoded func (fe *FlagEncoder) Set(name string, value bool) bool { for i, n := range fe.names { if i >= fe.nextIndex { @@ -37,6 +40,7 @@ func (fe *FlagEncoder) Set(name string, value bool) bool { return true } +// Sets sets multiple name values func (fe *FlagEncoder) Sets(values map[string]bool) bool { names := []string{} for n := range values { @@ -54,6 +58,7 @@ func (fe *FlagEncoder) Sets(values map[string]bool) bool { return true } +// Get returns the value for a given name func (fe FlagEncoder) Get(name string) (value, found bool) { var index int var n string @@ -69,6 +74,7 @@ func (fe FlagEncoder) Get(name string) (value, found bool) { const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789$#" +// Encode encodes the values into a single character func (fe FlagEncoder) Encode() byte { var flags byte for i, v := range fe.f { @@ -94,6 +100,7 @@ func (fe FlagEncoder) Encode() byte { return EncodeNumber64(flags) } +// Decode decodes the flags func Decode(flags byte, names ...string) *FlagEncoder { fe := &FlagEncoder{ nextIndex: len(names), @@ -124,10 +131,12 @@ func Decode(flags byte, names ...string) *FlagEncoder { return fe } +// EncodeNumber64 encodes a number up to 64 into a single character func EncodeNumber64(n byte) byte { return chars[n] } +// DecodeNumber64 decodes the passed character as a number func DecodeNumber64(e byte) (byte, bool) { for i, c := range chars { if e == byte(c) { diff --git a/shared/mytoken/event/event.go b/shared/mytoken/event/event.go index b87e6464..961a89bb 100644 --- a/shared/mytoken/event/event.go +++ b/shared/mytoken/event/event.go @@ -9,6 +9,7 @@ import ( "github.com/oidc-mytoken/server/shared/mytoken/pkg/mtid" ) +// MTEvent is type for mytoken events type MTEvent struct { *pkg.Event MTID mtid.MTID diff --git a/shared/mytoken/pkg/mtid/mtid.go b/shared/mytoken/pkg/mtid/mtid.go index 70974482..5cc1eb67 100644 --- a/shared/mytoken/pkg/mtid/mtid.go +++ b/shared/mytoken/pkg/mtid/mtid.go @@ -23,10 +23,12 @@ func New() MTID { } } +// Valid checks if the MTID is valid func (i *MTID) Valid() bool { return i.UUID.String() != "00000000-0000-0000-0000-000000000000" } +// HashValid checks if the MTID hash is valid func (i *MTID) HashValid() bool { if i.hash != "" { return true @@ -37,6 +39,7 @@ func (i *MTID) HashValid() bool { return i.Hash() != "" } +// Hash returns the MTID's hash func (i *MTID) Hash() string { if i.hash == "" && i.Valid() { i.hash = hashUtils.SHA512Str(i.Bytes()) @@ -64,10 +67,12 @@ func (i *MTID) Scan(src interface{}) error { return nil } +// MarshalJSON implements the json.Marshaler interface func (i MTID) MarshalJSON() ([]byte, error) { return json.Marshal(i.String()) } +// UnmarshalJSON implements the json.Unmarshaler interface func (i *MTID) UnmarshalJSON(data []byte) error { return json.Unmarshal(data, &i.UUID) } diff --git a/shared/mytoken/pkg/usedMytoken.go b/shared/mytoken/pkg/usedMytoken.go index d30df62e..7d45152f 100644 --- a/shared/mytoken/pkg/usedMytoken.go +++ b/shared/mytoken/pkg/usedMytoken.go @@ -12,6 +12,7 @@ type UsedMytoken struct { Restrictions []restrictions.UsedRestriction `json:"restrictions,omitempty"` } +// ToUsedMytoken turns a Mytoken into a UsedMytoken by adding information about its usages func (mt *Mytoken) ToUsedMytoken(tx *sqlx.Tx) (*UsedMytoken, error) { umt := &UsedMytoken{ Mytoken: *mt, diff --git a/shared/mytoken/restrictions/usedRestriction.go b/shared/mytoken/restrictions/usedRestriction.go index c624db55..5efdf59b 100644 --- a/shared/mytoken/restrictions/usedRestriction.go +++ b/shared/mytoken/restrictions/usedRestriction.go @@ -14,6 +14,7 @@ type UsedRestriction struct { UsagesOtherDone *int64 `json:"usages_other_done,omitempty"` } +// ToUsedRestrictions turns a Restrictions into a slice of UsedRestriction func (r Restrictions) ToUsedRestrictions(tx *sqlx.Tx, id mtid.MTID) (ur []UsedRestriction, err error) { var u UsedRestriction for _, rr := range r { @@ -26,6 +27,7 @@ func (r Restrictions) ToUsedRestrictions(tx *sqlx.Tx, id mtid.MTID) (ur []UsedRe return } +// ToUsedRestriction turns a Restriction into an UsedRestriction func (r Restriction) ToUsedRestriction(tx *sqlx.Tx, id mtid.MTID) (UsedRestriction, error) { ur := UsedRestriction{ Restriction: r, diff --git a/shared/mytoken/rotation/rotation.go b/shared/mytoken/rotation/rotation.go index e85b02fc..08b82f7b 100644 --- a/shared/mytoken/rotation/rotation.go +++ b/shared/mytoken/rotation/rotation.go @@ -4,4 +4,5 @@ import ( "github.com/oidc-mytoken/api/v0" ) +// Rotation is a type for specifying the rotation of mytokens type Rotation api.Rotation diff --git a/shared/utils/cryptUtils/cryptUtils.go b/shared/utils/cryptUtils/cryptUtils.go index 604e2984..bc9058aa 100644 --- a/shared/utils/cryptUtils/cryptUtils.go +++ b/shared/utils/cryptUtils/cryptUtils.go @@ -23,6 +23,7 @@ const ( const malFormedCiphertext = "malformed ciphertext" const malFormedCiphertextFmt = malFormedCiphertext + ": %s" +// RandomBytes returns size random bytes func RandomBytes(size int) []byte { r := make([]byte, size) if _, err := rand.Read(r); err != nil { @@ -38,41 +39,52 @@ func deriveKey(password string, salt []byte, size int) ([]byte, []byte) { return pbkdf2.Key([]byte(password), salt, keyIterations, size, sha512.New), salt } +// AES128Encrypt encrypts a string using AES128 with the passed password func AES128Encrypt(plain, password string) (string, error) { return aesEncrypt(plain, password, 16) } + +// AES192Encrypt encrypts a string using AES192 with the passed password func AES192Encrypt(plain, password string) (string, error) { return aesEncrypt(plain, password, 24) } + +// AES256Encrypt encrypts a string using AES256 with the passed password func AES256Encrypt(plain, password string) (string, error) { return aesEncrypt(plain, password, 32) } +// AES128Decrypt decrypts a string using AES128 with the passed password func AES128Decrypt(cipher, password string) (string, error) { return aesDecrypt(cipher, password, 16) } + +// AES192Decrypt decrypts a string using AES192 with the passed password func AES192Decrypt(cipher, password string) (string, error) { return aesDecrypt(cipher, password, 24) } + +// AES256Decrypt decrypts a string using AES256 with the passed password func AES256Decrypt(cipher, password string) (string, error) { return aesDecrypt(cipher, password, 32) } func aesEncrypt(plain, password string, keyLen int) (string, error) { key, salt := deriveKey(password, nil, keyLen) - cipher, err := AESEncrypt(plain, key) + ciph, err := AESEncrypt(plain, key) if err != nil { return "", err } - return fmt.Sprintf("%s-%s", base64.StdEncoding.EncodeToString(salt), cipher), nil + return fmt.Sprintf("%s-%s", base64.StdEncoding.EncodeToString(salt), ciph), nil } +// AESEncrypt encrypts a string using the passed key func AESEncrypt(plain string, key []byte) (string, error) { - cipher, nonce, err := aesE(plain, key) + ciph, nonce, err := aesE(plain, key) if err != nil { return "", err } - return fmt.Sprintf("%s-%s", base64.StdEncoding.EncodeToString(nonce), base64.StdEncoding.EncodeToString(cipher)), nil + return fmt.Sprintf("%s-%s", base64.StdEncoding.EncodeToString(nonce), base64.StdEncoding.EncodeToString(ciph)), nil } func aesDecrypt(cipher, password string, keyLen int) (string, error) { @@ -85,6 +97,7 @@ func aesDecrypt(cipher, password string, keyLen int) (string, error) { return AESDecrypt(arr[1], key) } +// AESDecrypt decrypts a string using the passed key func AESDecrypt(cipher string, key []byte) (string, error) { arr := strings.Split(cipher, "-") if len(arr) != 2 { diff --git a/shared/utils/jwtutils/jwtutils.go b/shared/utils/jwtutils/jwtutils.go index acf0aed1..981d0d79 100644 --- a/shared/utils/jwtutils/jwtutils.go +++ b/shared/utils/jwtutils/jwtutils.go @@ -1,7 +1,7 @@ package jwtutils import ( - "github.com/dgrijalva/jwt-go" + jwt "github.com/dgrijalva/jwt-go" log "github.com/sirupsen/logrus" ) @@ -11,6 +11,7 @@ type ResultSet []struct { Set bool } +// GetFromJWT returns the values for the requested keys from the JWT func GetFromJWT(token string, key ...string) (values ResultSet) { if atJWT, _ := jwt.Parse(token, nil); atJWT != nil { log.Trace("Parsed token") @@ -27,6 +28,7 @@ func GetFromJWT(token string, key ...string) (values ResultSet) { return values } +// GetStringFromJWT returns a string value for the given key func GetStringFromJWT(token, key string) (string, bool) { res := GetFromJWT(token, key) if len(res) != 1 { diff --git a/shared/utils/unixtime/unixtime.go b/shared/utils/unixtime/unixtime.go index e43ec908..c59c758f 100644 --- a/shared/utils/unixtime/unixtime.go +++ b/shared/utils/unixtime/unixtime.go @@ -8,14 +8,20 @@ import ( "github.com/oidc-mytoken/server/shared/utils" ) +// UnixTime is a type for a Unix Timestamp type UnixTime int64 +// Time returns the UnixTime as time.Time func (t UnixTime) Time() time.Time { return time.Unix(int64(t), 0) } + +// New creates a new UnixTime from a time.Time func New(t time.Time) UnixTime { return UnixTime(t.Unix()) } + +// Now returns the current time as UnixTime func Now() UnixTime { return New(time.Now()) } From c1ddd7718e9eb74b1b56049dba32fea832eb744c Mon Sep 17 00:00:00 2001 From: zachmann Date: Fri, 11 Jun 2021 18:03:17 +0200 Subject: [PATCH 19/61] fix deprecated use of mysql.NullTime --- internal/db/dbrepo/versionrepo/versionrepo.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/internal/db/dbrepo/versionrepo/versionrepo.go b/internal/db/dbrepo/versionrepo/versionrepo.go index 002b39bb..5b8013e9 100644 --- a/internal/db/dbrepo/versionrepo/versionrepo.go +++ b/internal/db/dbrepo/versionrepo/versionrepo.go @@ -1,10 +1,10 @@ package versionrepo import ( + "database/sql" "sort" "strings" - "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" "github.com/oidc-mytoken/server/internal/db" "github.com/oidc-mytoken/server/internal/db/dbmigrate" @@ -32,8 +32,8 @@ func SetVersionAfter(tx *sqlx.Tx, version string) error { // UpdateTimes is a type for checking if the db migration commands for different mytoken version have been executed type UpdateTimes struct { Version string - Before mysql.NullTime `db:"bef"` - After mysql.NullTime `db:"aft"` + Before sql.NullTime `db:"bef"` + After sql.NullTime `db:"aft"` } // DBVersionState describes the version state of the db From 713f302f54778dfbbfd723ef1068c7f304de3d08 Mon Sep 17 00:00:00 2001 From: zachmann Date: Thu, 8 Jul 2021 11:57:12 +0200 Subject: [PATCH 20/61] fix db migration version check --- internal/db/dbrepo/versionrepo/versionrepo.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/internal/db/dbrepo/versionrepo/versionrepo.go b/internal/db/dbrepo/versionrepo/versionrepo.go index 5b8013e9..bf782da4 100644 --- a/internal/db/dbrepo/versionrepo/versionrepo.go +++ b/internal/db/dbrepo/versionrepo/versionrepo.go @@ -48,7 +48,7 @@ func (state DBVersionState) Swap(i, j int) { state[i], state[j] = state[j], stat // Less checks if a version is less than another func (state DBVersionState) Less(i, j int) bool { a, b := state[i].Version, state[j].Version - return semver.Compare(a, b) < 0 + return semver.Compare("v"+a, "v"+b) < 0 } // Sort sorts this DBVersionState by the version @@ -57,19 +57,20 @@ func (state DBVersionState) Sort() { } // dbHasAllVersions checks that the database is compatible with the current version; assumes that DBVersionState is ordered -func (state DBVersionState) dBHasAllVersions() bool { +func (state DBVersionState) dBHasAllVersions() (hasAllVersions bool, missingVersions []string) { for v, cmds := range dbmigrate.Migrate { if !state.dBHasVersion(v, cmds) { - return false + missingVersions = append(missingVersions, v) } } - return true + hasAllVersions = len(missingVersions) == 0 + return } // dbHasVersion checks that the database is compatible with the passed version; assumes that DBVersionState is ordered func (state DBVersionState) dBHasVersion(v string, cmds dbmigrate.Commands) bool { i := sort.Search(len(state), func(i int) bool { - return semver.Compare(state[i].Version, v) >= 0 + return semver.Compare("v"+state[i].Version, "v"+v) >= 0 }) if i >= len(state) || state[i].Version != v { // we have to check that i really points to v return false @@ -103,7 +104,7 @@ func ConnectToVersion() { if err != nil { log.WithError(err).Fatal() } - if !state.dBHasAllVersions() { - log.WithField("version", version.VERSION()).Fatal("database schema not updated to this server version") + if hasAllVersions, missingVersions := state.dBHasAllVersions(); !hasAllVersions { + log.WithField("server_version", version.VERSION()).WithField("missing_versions_in_db", missingVersions).Fatal("database schema not updated to this server version") } } From be75762ac35aa15b6d0d14489953c61827e26da7 Mon Sep 17 00:00:00 2001 From: zachmann Date: Fri, 9 Jul 2021 09:31:42 +0200 Subject: [PATCH 21/61] fix db logging --- internal/db/cluster/cluster.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/internal/db/cluster/cluster.go b/internal/db/cluster/cluster.go index 03b61039..88313bad 100644 --- a/internal/db/cluster/cluster.go +++ b/internal/db/cluster/cluster.go @@ -43,7 +43,7 @@ type Cluster struct { type node struct { db *sqlx.DB - dsn string + host string active bool // lock sync.RWMutex } @@ -68,19 +68,19 @@ func (c *Cluster) AddNodes() { // AddNode adds the passed host a a db node to the cluster func (c *Cluster) AddNode(host string) error { log.WithField("host", host).Debug("Adding node to db cluster") - dsn := fmt.Sprintf("%s:%s@%s(%s)/%s?parseTime=true", c.conf.User, c.conf.GetPassword(), "tcp", host, c.conf.DB) return c.addNode(&node{ - dsn: dsn, + host: host, }) } func (c *Cluster) addNode(n *node) error { n.close() - db, err := connectDSN(n.dsn) + dsn := fmt.Sprintf("%s:%s@%s(%s)/%s?parseTime=true", c.conf.User, c.conf.GetPassword(), "tcp", n.host, c.conf.DB) + db, err := connectDSN(dsn) if err != nil { n.active = false c.down <- n - log.WithField("dsn", n.dsn).Debug("Could not connect node") + log.WithField("dsn", dsn).Debug("Could not connect node") return err } n.db = db @@ -179,7 +179,7 @@ func (n *node) transact(fn func(*sqlx.Tx) error) (bool, error) { case e == "sql: database is closed", strings.HasPrefix(e, "dial tcp"), strings.HasSuffix(e, "closing bad idle connection: EOF"): - log.WithField("dsn", n.dsn).Error("Node is down") + log.WithField("host", n.host).Error("Node is down") return true, err } } @@ -201,15 +201,15 @@ func (n *node) trans(fn func(*sqlx.Tx) error) error { } func (c *Cluster) next() *node { - log.Debug("Selecting a node") + log.Trace("Selecting a node") select { case n := <-c.active: if n.active { c.active <- n - log.WithField("dsn", n.dsn).Debug("Selected active node") + log.WithField("host", n.host).Trace("Selected active node") return n } - log.WithField("dsn", n.dsn).Debug("Found inactive node") + log.WithField("host", n.host).Trace("Found inactive node") go c.addNode(n) // try to add node again, if it does not work, will add to down nodes return c.next() default: From 3a4d97ee09337bddd424f25893ba98705cef57eb Mon Sep 17 00:00:00 2001 From: zachmann Date: Fri, 9 Jul 2021 11:23:38 +0200 Subject: [PATCH 22/61] implement token rotation; fix other stuff --- config/example-config.yaml | 4 + docker/example-docker-config.yaml | 4 + go.mod | 2 +- go.sum | 3 +- internal/config/config.go | 2 + internal/db/dbmigrate/migrate.go | 1 + internal/db/dbmigrate/v0.3.0.go | 10 +++ .../dbrepo/authcodeinforepo/authcodeInfo.go | 8 +- .../mytokenrepo/mytokenrepohelper/helpers.go | 12 ++- .../dbrepo/refreshtokenrepo/refreshtoken.go | 50 +++++++++--- .../revocation/revocationEndpoint.go | 3 +- .../token/access/accessTokenEndpoint.go | 40 ++++++--- .../token/access/pkg/accessTokenRequest.go | 15 +++- .../token/access/pkg/accessTokenResponse.go | 13 +++ .../token/mytoken/pkg/myTokenRequest.go | 11 +-- .../token/mytoken/pkg/myTokenResponse.go | 1 + .../token/mytoken/pkg/oidcFlowRequest.go | 1 + .../token/mytoken/pkg/pollingCodeRequest.go | 7 ++ .../token/mytoken/pkg/transferCodeRequest.go | 7 ++ .../token/mytoken/polling/pollingEndpoint.go | 2 +- internal/endpoints/tokeninfo/history.go | 21 ++++- internal/endpoints/tokeninfo/introspect.go | 2 +- internal/endpoints/tokeninfo/list.go | 20 ++++- .../tokeninfo/pkg/tokeninfoHistoryResponse.go | 2 + .../tokeninfo/pkg/tokeninfoListResponse.go | 4 +- .../tokeninfo/pkg/tokeninfoRequest.go | 6 +- .../tokeninfo/pkg/tokeninfoTreeResponse.go | 6 +- internal/endpoints/tokeninfo/tokeninfo.go | 14 ++-- internal/endpoints/tokeninfo/tree.go | 20 ++++- internal/oidc/authcode/authcode.go | 25 +++--- internal/server/web/static/js/login.js | 6 +- internal/utils/cookies/cookies.go | 38 +++++++++ internal/utils/ctxUtils/token.go | 6 +- shared/mytoken/event/pkg/event.go | 24 +++++- shared/mytoken/mytokenHandler.go | 59 ++++++++------ shared/mytoken/pkg/mytoken.go | 24 ++++-- shared/mytoken/restrictions/restriction.go | 2 +- shared/mytoken/rotation/rotation.go | 81 ++++++++++++++++++- shared/mytoken/token/token.go | 36 --------- .../universalmytoken/universalmytoken.go | 52 ++++++++++++ 40 files changed, 494 insertions(+), 150 deletions(-) create mode 100644 internal/db/dbmigrate/v0.3.0.go create mode 100644 internal/endpoints/token/access/pkg/accessTokenResponse.go create mode 100644 internal/utils/cookies/cookies.go delete mode 100644 shared/mytoken/token/token.go create mode 100644 shared/mytoken/universalmytoken/universalmytoken.go diff --git a/config/example-config.yaml b/config/example-config.yaml index 690efaa8..86ffa256 100644 --- a/config/example-config.yaml +++ b/config/example-config.yaml @@ -130,6 +130,10 @@ features: expires_after: 300 # The time in seconds how long a polling code can be used polling_interval: 5 # The interval in seconds the native application should wait between two polling attempts + # Support for rotation mytokens; users can enable rotation/disable rotation for their mytokens; if enabled a new mytoken will be returned after each usages and old mytokens can no longer be used. + token_rotation: + enabled: true + # Support for the access_token grant, i.e. a user can use an AT to obtain an ST. access_token_grant: enabled: true diff --git a/docker/example-docker-config.yaml b/docker/example-docker-config.yaml index 8d013036..2082c466 100644 --- a/docker/example-docker-config.yaml +++ b/docker/example-docker-config.yaml @@ -132,6 +132,10 @@ features: expires_after: 300 # The time in seconds how long a polling code can be used polling_interval: 5 # The interval in seconds the native application should wait between two polling attempts + # Support for rotation mytokens; users can enable rotation/disable rotation for their mytokens; if enabled a new mytoken will be returned after each usages and old mytokens can no longer be used. + token_rotation: + enabled: true + # Support for the access_token grant, i.e. a user can use an AT to obtain an ST. access_token_grant: enabled: true diff --git a/go.mod b/go.mod index ad894a7a..a0113a58 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/jinzhu/copier v0.3.2 github.com/jmoiron/sqlx v1.3.4 github.com/lestrrat-go/jwx v1.2.1 - github.com/oidc-mytoken/api v0.0.0-20210610140005-29f166a942ec + github.com/oidc-mytoken/api v0.0.0-20210708152538-f4648ff470f6 github.com/oidc-mytoken/lib v0.2.1-0.20210611134913-0c477174fc1f github.com/satori/go.uuid v1.2.0 github.com/sirupsen/logrus v1.8.1 diff --git a/go.sum b/go.sum index 1c517e83..7f03035c 100644 --- a/go.sum +++ b/go.sum @@ -413,8 +413,9 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nishanths/exhaustive v0.0.0-20200525081945-8e46705b6132 h1:NjznefjSrral0MiR4KlB41io/d3OklvhcgQUdfZTqJE= github.com/nishanths/exhaustive v0.0.0-20200525081945-8e46705b6132/go.mod h1:wBEpHwM2OdmeNpdCvRPUlkEbBuaFmcK4Wv8Q7FuGW3c= -github.com/oidc-mytoken/api v0.0.0-20210610140005-29f166a942ec h1:DGLeZJnsVWq6y81d6AH9mo+J5dQO47VFH9K2bKYgAjk= github.com/oidc-mytoken/api v0.0.0-20210610140005-29f166a942ec/go.mod h1:S8t1XA42EFAgc3vUfis0g1LPGA4TXH0nfDynvgo6cwk= +github.com/oidc-mytoken/api v0.0.0-20210708152538-f4648ff470f6 h1:POIAmQRwpU3FZm5+84iDRSTdN5QB68jNTGEU/T+4cQU= +github.com/oidc-mytoken/api v0.0.0-20210708152538-f4648ff470f6/go.mod h1:S8t1XA42EFAgc3vUfis0g1LPGA4TXH0nfDynvgo6cwk= github.com/oidc-mytoken/lib v0.2.1-0.20210611134913-0c477174fc1f h1:EqbUYQ45C2iTLHZ0mqtAK9+SxMSemj9gaAv9/csN9D0= github.com/oidc-mytoken/lib v0.2.1-0.20210611134913-0c477174fc1f/go.mod h1:kwotzLmyV5WEsvU50JoF8Q5+rjLFhjQKAgBGoEw7FRE= github.com/oidc-mytoken/server v0.2.0/go.mod h1:6uFm+Za9NMK3gq4OOIeX3gs3T6leluVIWsGiM1zlQbA= diff --git a/internal/config/config.go b/internal/config/config.go index 2dc1a77f..c8f6f74a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -67,6 +67,7 @@ var defaultConfig = Config{ PollingCodeExpiresAfter: 300, PollingInterval: 5, }, + TokenRotation: onlyEnable{true}, AccessTokenGrant: onlyEnable{true}, SignedJWTGrant: onlyEnable{true}, TokenInfo: tokeninfoConfig{ @@ -109,6 +110,7 @@ type featuresConf struct { ShortTokens shortTokenConfig `yaml:"short_tokens"` TransferCodes onlyEnable `yaml:"transfer_codes"` Polling pollingConf `yaml:"polling_codes"` + TokenRotation onlyEnable `yaml:"token_rotation"` AccessTokenGrant onlyEnable `yaml:"access_token_grant"` SignedJWTGrant onlyEnable `yaml:"signed_jwt_grant"` TokenInfo tokeninfoConfig `yaml:"tokeninfo"` diff --git a/internal/db/dbmigrate/migrate.go b/internal/db/dbmigrate/migrate.go index 95b36b3e..72aec79f 100644 --- a/internal/db/dbmigrate/migrate.go +++ b/internal/db/dbmigrate/migrate.go @@ -12,4 +12,5 @@ type VersionCommands map[string]Commands // Migrate holds the VersionCommands for mytoken. These commands are used to migrate the database between mytoken versions. var Migrate = VersionCommands{ "0.2.0": {Before: v0_2_0_Before}, + "0.3.0": {Before: v0_3_0_Before}, } diff --git a/internal/db/dbmigrate/v0.3.0.go b/internal/db/dbmigrate/v0.3.0.go new file mode 100644 index 00000000..c35ebf75 --- /dev/null +++ b/internal/db/dbmigrate/v0.3.0.go @@ -0,0 +1,10 @@ +package dbmigrate + +var v0_3_0_Before = []string{ + // Tables + "ALTER TABLE AuthInfo ADD rotation json NULL", + "DROP TRIGGER updtrigger", + + // Predefined Values + "INSERT IGNORE INTO Events (event) VALUES('token_rotated')", +} diff --git a/internal/db/dbrepo/authcodeinforepo/authcodeInfo.go b/internal/db/dbrepo/authcodeinforepo/authcodeInfo.go index caddf1d9..917e36e5 100644 --- a/internal/db/dbrepo/authcodeinforepo/authcodeInfo.go +++ b/internal/db/dbrepo/authcodeinforepo/authcodeInfo.go @@ -27,6 +27,7 @@ type AuthFlowInfoOut struct { SubtokenCapabilities api.Capabilities Name string PollingCode bool + Rotation *api.Rotation } type authFlowInfo struct { @@ -38,6 +39,7 @@ type authFlowInfo struct { Name db.NullString PollingCode db.BitBool `db:"polling_code"` ExpiresIn int64 `db:"expires_in"` + Rotation *api.Rotation } func (i *AuthFlowInfo) toAuthFlowInfo() *authFlowInfo { @@ -50,6 +52,7 @@ func (i *AuthFlowInfo) toAuthFlowInfo() *authFlowInfo { Name: db.NewNullString(i.Name), ExpiresIn: config.Get().Features.Polling.PollingCodeExpiresAfter, PollingCode: i.PollingCode != nil, + Rotation: i.Rotation, } } @@ -62,6 +65,7 @@ func (i *authFlowInfo) toAuthFlowInfo() *AuthFlowInfoOut { SubtokenCapabilities: i.SubtokenCapabilities, Name: i.Name.String, PollingCode: bool(i.PollingCode), + Rotation: i.Rotation, } } @@ -75,7 +79,7 @@ func (i *AuthFlowInfo) Store(tx *sqlx.Tx) error { return err } } - _, err := tx.NamedExec(`INSERT INTO AuthInfo (state_h, iss, restrictions, capabilities, subtoken_capabilities, name, expires_in, polling_code) VALUES(:state_h, :iss, :restrictions, :capabilities, :subtoken_capabilities, :name, :expires_in, :polling_code)`, store) + _, err := tx.NamedExec(`INSERT INTO AuthInfo (state_h, iss, restrictions, capabilities, subtoken_capabilities, name, expires_in, polling_code, rotation) VALUES(:state_h, :iss, :restrictions, :capabilities, :subtoken_capabilities, :name, :expires_in, :polling_code, :rotation)`, store) return err }) } @@ -84,7 +88,7 @@ func (i *AuthFlowInfo) Store(tx *sqlx.Tx) error { func GetAuthFlowInfoByState(state *state.State) (*AuthFlowInfoOut, error) { info := authFlowInfo{} if err := db.Transact(func(tx *sqlx.Tx) error { - return tx.Get(&info, `SELECT state_h, iss, restrictions, capabilities, subtoken_capabilities, name, polling_code FROM AuthInfo WHERE state_h=? AND expires_at >= CURRENT_TIMESTAMP()`, state) + return tx.Get(&info, `SELECT state_h, iss, restrictions, capabilities, subtoken_capabilities, name, polling_code, rotation FROM AuthInfo WHERE state_h=? AND expires_at >= CURRENT_TIMESTAMP()`, state) }); err != nil { return nil, err } diff --git a/internal/db/dbrepo/mytokenrepo/mytokenrepohelper/helpers.go b/internal/db/dbrepo/mytokenrepo/mytokenrepohelper/helpers.go index 1eda3bb0..028df5b5 100644 --- a/internal/db/dbrepo/mytokenrepo/mytokenrepohelper/helpers.go +++ b/internal/db/dbrepo/mytokenrepo/mytokenrepohelper/helpers.go @@ -5,11 +5,11 @@ import ( "errors" "github.com/jmoiron/sqlx" + "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/db" "github.com/oidc-mytoken/server/internal/utils/hashUtils" "github.com/oidc-mytoken/server/shared/mytoken/pkg/mtid" - "github.com/oidc-mytoken/server/shared/mytoken/rotation" ) // ParseError parses the passed error for a sql.ErrNoRows @@ -62,7 +62,7 @@ func recursiveRevokeMT(tx *sqlx.Tx, id mtid.MTID) error { } // CheckTokenRevoked checks if a Mytoken has been revoked. -func CheckTokenRevoked(tx *sqlx.Tx, id mtid.MTID, seqno uint64, rot *rotation.Rotation) (bool, error) { +func CheckTokenRevoked(tx *sqlx.Tx, id mtid.MTID, seqno uint64, rot *api.Rotation) (bool, error) { if rot != nil && rot.Lifetime > 0 { return checkRotatingTokenRevoked(tx, id, seqno, rot.Lifetime) } @@ -95,6 +95,14 @@ func checkRotatingTokenRevoked(tx *sqlx.Tx, id mtid.MTID, seqno, rotationLifetim return true, nil } +// UpdateSeqNo updates the sequence number of a mytoken, i.e. it rotates the mytoken. Don't forget to update the encryption key +func UpdateSeqNo(tx *sqlx.Tx, id mtid.MTID, seqno uint64) error { + return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { + _, err := tx.Exec(`UPDATE MTokens SET seqno=?, last_rotated=current_timestamp() WHERE id=?`, seqno, id) + return err + }) +} + // revokeMT revokes the passed mytoken but no children func revokeMT(tx *sqlx.Tx, id mtid.MTID) error { return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { diff --git a/internal/db/dbrepo/refreshtokenrepo/refreshtoken.go b/internal/db/dbrepo/refreshtokenrepo/refreshtoken.go index fcd27b31..0cc87b5e 100644 --- a/internal/db/dbrepo/refreshtokenrepo/refreshtoken.go +++ b/internal/db/dbrepo/refreshtokenrepo/refreshtoken.go @@ -4,7 +4,6 @@ import ( "encoding/base64" "github.com/jmoiron/sqlx" - "github.com/oidc-mytoken/server/internal/db" helper "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/mytokenrepohelper" "github.com/oidc-mytoken/server/shared/mytoken/pkg/mtid" @@ -27,24 +26,45 @@ func UpdateRefreshToken(tx *sqlx.Tx, tokenID mtid.MTID, newRT, jwt string) error }) } -// GetEncryptionKey returns the encryption key and its id for a mytoken -func GetEncryptionKey(tx *sqlx.Tx, tokenID mtid.MTID, jwt string) ([]byte, uint64, error) { - var key []byte - var rtID uint64 - err := db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { +// ReencryptEncryptionKey re-encrypts the encryption key for a mytoken. This is needed when the mytoken changes, e.g. on token rotation +func ReencryptEncryptionKey(tx *sqlx.Tx, tokenID mtid.MTID, oldJWT, newJWT string) error { + return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { + keyID, err := getEncryptionKeyID(tx, tokenID) + if err != nil { + return err + } + var encryptedKey string + if err = tx.Get(&encryptedKey, `SELECT encryption_key FROM EncryptionKeys WHERE id=?`, keyID); err != nil { + return err + } + key, err := cryptUtils.AES256Decrypt(encryptedKey, oldJWT) + if err != nil { + return err + } + updatedKey, err := cryptUtils.AES256Encrypt(key, newJWT) + if err != nil { + return err + } + _, err = tx.Exec(`UPDATE EncryptionKeys SET encryption_key=? WHERE id=?`, updatedKey, keyID) + return err + }) +} + +// GetEncryptionKey returns the encryption key and the rtid for a mytoken +func GetEncryptionKey(tx *sqlx.Tx, tokenID mtid.MTID, jwt string) (key []byte, rtID uint64, err error) { + err = db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { var res struct { EncryptedKey encryptionKey `db:"encryption_key"` ID uint64 `db:"rt_id"` } - if err := tx.Get(&res, `SELECT encryption_key, rt_id FROM MyTokens WHERE id=?`, tokenID); err != nil { + if err = tx.Get(&res, `SELECT encryption_key, rt_id FROM MyTokens WHERE id=?`, tokenID); err != nil { return err } rtID = res.ID - tmp, err := res.EncryptedKey.decrypt(jwt) - key = tmp + key, err = res.EncryptedKey.decrypt(jwt) return err }) - return key, rtID, err + return } type encryptionKey string @@ -102,7 +122,15 @@ func CountRTOccurrences(tx *sqlx.Tx, rtID uint64) (count int, err error) { // GetRTID returns the refresh token id for a mytoken func GetRTID(tx *sqlx.Tx, myID mtid.MTID) (rtID uint64, err error) { err = db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { - return tx.Get(&rtID, `SELECT rt_ID FROM MTokens WHERE id=?`, myID) + return tx.Get(&rtID, `SELECT rt_id FROM MTokens WHERE id=?`, myID) + }) + return +} + +// getEncryptionKeyID returns the id of the encryption key used for encrypting the RT linked to this mytoken +func getEncryptionKeyID(tx *sqlx.Tx, myID mtid.MTID) (keyID uint64, err error) { + err = db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { + return tx.Get(&keyID, `SELECT key_id FROM RT_EncryptionKeys WHERE MT_id=? AND rt_id=(SELECT rt_id FROM MTokens WHERE id=?)`, myID, myID) }) return } diff --git a/internal/endpoints/revocation/revocationEndpoint.go b/internal/endpoints/revocation/revocationEndpoint.go index 25191bb8..c2af7e1a 100644 --- a/internal/endpoints/revocation/revocationEndpoint.go +++ b/internal/endpoints/revocation/revocationEndpoint.go @@ -17,7 +17,6 @@ import ( sharedModel "github.com/oidc-mytoken/server/shared/model" "github.com/oidc-mytoken/server/shared/mytoken" mytokenPkg "github.com/oidc-mytoken/server/shared/mytoken/pkg" - "github.com/oidc-mytoken/server/shared/mytoken/token" "github.com/oidc-mytoken/server/shared/utils" ) @@ -92,7 +91,7 @@ func revokeMytoken(tx *sqlx.Tx, jwt, issuer string, recursive bool) (errRes *mod Response: sharedModel.BadRequestError("token not for specified issuer"), } } - return mytoken.RevokeMytoken(tx, mt.ID, token.Token(jwt), recursive, mt.OIDCIssuer) + return mytoken.RevokeMytoken(tx, mt.ID, jwt, recursive, mt.OIDCIssuer) } func revokeTransferCode(tx *sqlx.Tx, token, issuer string) (errRes *model.Response) { diff --git a/internal/endpoints/token/access/accessTokenEndpoint.go b/internal/endpoints/token/access/accessTokenEndpoint.go index cf6efca4..228766b1 100644 --- a/internal/endpoints/token/access/accessTokenEndpoint.go +++ b/internal/endpoints/token/access/accessTokenEndpoint.go @@ -5,6 +5,9 @@ import ( "github.com/gofiber/fiber/v2" "github.com/jmoiron/sqlx" + response "github.com/oidc-mytoken/server/internal/endpoints/token/mytoken/pkg" + "github.com/oidc-mytoken/server/internal/utils/cookies" + "github.com/oidc-mytoken/server/shared/mytoken/rotation" log "github.com/sirupsen/logrus" "github.com/oidc-mytoken/api/v0" @@ -22,7 +25,7 @@ import ( event "github.com/oidc-mytoken/server/shared/mytoken/event/pkg" mytoken "github.com/oidc-mytoken/server/shared/mytoken/pkg" "github.com/oidc-mytoken/server/shared/mytoken/restrictions" - "github.com/oidc-mytoken/server/shared/mytoken/token" + "github.com/oidc-mytoken/server/shared/mytoken/universalmytoken" "github.com/oidc-mytoken/server/shared/utils" "github.com/oidc-mytoken/server/shared/utils/jwtutils" ) @@ -30,12 +33,12 @@ import ( // HandleAccessTokenEndpoint handles request on the access token endpoint func HandleAccessTokenEndpoint(ctx *fiber.Ctx) error { log.Debug("Handle access token request") - req := request.AccessTokenRequest{} + req := request.NewAccessTokenRequest() if err := ctx.BodyParser(&req); err != nil { return serverModel.ErrorToBadRequestErrorResponse(err).Send(ctx) } log.Trace("Parsed access token request") - if req.Mytoken == "" { + if req.Mytoken.JWT == "" { req.Mytoken = req.RefreshToken } @@ -47,9 +50,9 @@ func HandleAccessTokenEndpoint(ctx *fiber.Ctx) error { return res.Send(ctx) } log.Trace("Checked grant type") - if len(req.Mytoken) == 0 { + if req.Mytoken.JWT == "" { var err error - req.Mytoken, err = token.GetLongMytoken(ctx.Cookies("mytoken")) + req.Mytoken, err = universalmytoken.Parse(ctx.Cookies("mytoken")) if err != nil { return serverModel.Response{ Status: fiber.StatusUnauthorized, @@ -58,7 +61,7 @@ func HandleAccessTokenEndpoint(ctx *fiber.Ctx) error { } } - mt, err := mytoken.ParseJWT(string(req.Mytoken)) + mt, err := mytoken.ParseJWT(req.Mytoken.JWT) if err != nil { return (&serverModel.Response{ Status: fiber.StatusUnauthorized, @@ -140,7 +143,7 @@ func handleAccessTokenRefresh(mt *mytoken.Mytoken, req request.AccessTokenReques auds = strings.Join(usedRestriction.Audiences, " ") } } - rt, rtFound, dbErr := refreshtokenrepo.GetRefreshToken(nil, mt.ID, string(req.Mytoken)) + rt, rtFound, dbErr := refreshtokenrepo.GetRefreshToken(nil, mt.ID, req.Mytoken.JWT) if dbErr != nil { return serverModel.ErrorToInternalServerErrorResponse(dbErr) } @@ -151,7 +154,7 @@ func handleAccessTokenRefresh(mt *mytoken.Mytoken, req request.AccessTokenReques } } - oidcRes, oidcErrRes, err := refresh.RefreshFlowAndUpdateDB(provider, mt.ID, string(req.Mytoken), rt, scopes, auds) + oidcRes, oidcErrRes, err := refresh.RefreshFlowAndUpdateDB(provider, mt.ID, req.Mytoken.JWT, rt, scopes, auds) if err != nil { return serverModel.ErrorToInternalServerErrorResponse(err) } @@ -174,6 +177,7 @@ func handleAccessTokenRefresh(mt *mytoken.Mytoken, req request.AccessTokenReques Scopes: utils.SplitIgnoreEmpty(retScopes, " "), Audiences: retAudiences, } + var tokenUpdate *response.MytokenResponse if err = db.Transact(func(tx *sqlx.Tx) error { if err = at.Store(tx); err != nil { return err @@ -189,13 +193,14 @@ func handleAccessTokenRefresh(mt *mytoken.Mytoken, req request.AccessTokenReques return err } } - return nil + tokenUpdate, err = rotation.RotateMytokenAfterOtherForResponse(tx, req.Mytoken.JWT, mt, networkData, req.Mytoken.OriginalTokenType) + return err }); err != nil { return serverModel.ErrorToInternalServerErrorResponse(err) } - return &serverModel.Response{ - Status: fiber.StatusOK, - Response: api.AccessTokenResponse{ + + rsp := request.AccessTokenResponse{ + AccessTokenResponse: api.AccessTokenResponse{ AccessToken: oidcRes.AccessToken, TokenType: oidcRes.TokenType, ExpiresIn: oidcRes.ExpiresIn, @@ -203,4 +208,15 @@ func handleAccessTokenRefresh(mt *mytoken.Mytoken, req request.AccessTokenReques Audiences: retAudiences, }, } + var cake []*fiber.Cookie + if tokenUpdate != nil { + rsp.TokenUpdate = tokenUpdate + cookie := cookies.MytokenCookie(tokenUpdate.Mytoken) + cake = []*fiber.Cookie{&cookie} + } + return &serverModel.Response{ + Status: fiber.StatusOK, + Response: rsp, + Cookies: cake, + } } diff --git a/internal/endpoints/token/access/pkg/accessTokenRequest.go b/internal/endpoints/token/access/pkg/accessTokenRequest.go index ab7b707a..5150e2b7 100644 --- a/internal/endpoints/token/access/pkg/accessTokenRequest.go +++ b/internal/endpoints/token/access/pkg/accessTokenRequest.go @@ -3,13 +3,20 @@ package pkg import ( "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/shared/model" - "github.com/oidc-mytoken/server/shared/mytoken/token" + "github.com/oidc-mytoken/server/shared/mytoken/universalmytoken" ) // AccessTokenRequest holds an request for an access token type AccessTokenRequest struct { api.AccessTokenRequest `json:",inline"` - GrantType model.GrantType `json:"grant_type" xml:"grant_type" form:"grant_type"` - Mytoken token.Token `json:"mytoken" xml:"mytoken" form:"mytoken"` - RefreshToken token.Token `json:"refresh_token" xml:"refresh_token" form:"refresh_token"` + GrantType model.GrantType `json:"grant_type" xml:"grant_type" form:"grant_type"` + Mytoken universalmytoken.UniversalMytoken `json:"mytoken" xml:"mytoken" form:"mytoken"` + RefreshToken universalmytoken.UniversalMytoken `json:"refresh_token" xml:"refresh_token" form:"refresh_token"` +} + +// NewAccessTokenRequest returns a new AccessTokenRequest +func NewAccessTokenRequest() AccessTokenRequest { + return AccessTokenRequest{ + GrantType: -1, // This value will remain if grant_type is not contained in the request. We have to set it to -1, because the default of 0 would be a valid GrantType + } } diff --git a/internal/endpoints/token/access/pkg/accessTokenResponse.go b/internal/endpoints/token/access/pkg/accessTokenResponse.go new file mode 100644 index 00000000..feac377b --- /dev/null +++ b/internal/endpoints/token/access/pkg/accessTokenResponse.go @@ -0,0 +1,13 @@ +package pkg + +import ( + "github.com/oidc-mytoken/api/v0" + my "github.com/oidc-mytoken/server/internal/endpoints/token/mytoken/pkg" +) + +// AccessTokenResponse is type for responses for access token requests +type AccessTokenResponse struct { + // un update check api.AccessTokenResponse + api.AccessTokenResponse + TokenUpdate *my.MytokenResponse `json:"token_update,omitempty"` +} diff --git a/internal/endpoints/token/mytoken/pkg/myTokenRequest.go b/internal/endpoints/token/mytoken/pkg/myTokenRequest.go index ee37561e..72da66d7 100644 --- a/internal/endpoints/token/mytoken/pkg/myTokenRequest.go +++ b/internal/endpoints/token/mytoken/pkg/myTokenRequest.go @@ -6,22 +6,23 @@ import ( "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/shared/model" "github.com/oidc-mytoken/server/shared/mytoken/restrictions" - "github.com/oidc-mytoken/server/shared/mytoken/token" + "github.com/oidc-mytoken/server/shared/mytoken/universalmytoken" ) // MytokenFromMytokenRequest is a request to create a new Mytoken from an existing Mytoken type MytokenFromMytokenRequest struct { api.MytokenFromMytokenRequest `json:",inline"` - GrantType model.GrantType `json:"grant_type"` - Mytoken token.Token `json:"mytoken"` - Restrictions restrictions.Restrictions `json:"restrictions"` - ResponseType model.ResponseType `json:"response_type"` + GrantType model.GrantType `json:"grant_type"` + Mytoken universalmytoken.UniversalMytoken `json:"mytoken"` + Restrictions restrictions.Restrictions `json:"restrictions"` + ResponseType model.ResponseType `json:"response_type"` } // NewMytokenRequest creates a MytokenFromMytokenRequest with the default values where they can be omitted func NewMytokenRequest() *MytokenFromMytokenRequest { return &MytokenFromMytokenRequest{ ResponseType: model.ResponseTypeToken, + GrantType: -1, } } diff --git a/internal/endpoints/token/mytoken/pkg/myTokenResponse.go b/internal/endpoints/token/mytoken/pkg/myTokenResponse.go index f01b6a83..f5ade0d5 100644 --- a/internal/endpoints/token/mytoken/pkg/myTokenResponse.go +++ b/internal/endpoints/token/mytoken/pkg/myTokenResponse.go @@ -11,4 +11,5 @@ type MytokenResponse struct { api.MytokenResponse `json:",inline"` MytokenType model.ResponseType `json:"mytoken_type"` Restrictions restrictions.Restrictions `json:"restrictions,omitempty"` + TokenUpdate *MytokenResponse `json:"token_update,omitempty"` } diff --git a/internal/endpoints/token/mytoken/pkg/oidcFlowRequest.go b/internal/endpoints/token/mytoken/pkg/oidcFlowRequest.go index cd714953..1c75950f 100644 --- a/internal/endpoints/token/mytoken/pkg/oidcFlowRequest.go +++ b/internal/endpoints/token/mytoken/pkg/oidcFlowRequest.go @@ -28,6 +28,7 @@ func NewOIDCFlowRequest() *OIDCFlowRequest { }, ResponseType: model.ResponseTypeToken, redirectType: redirectTypeWeb, + GrantType: -1, } } diff --git a/internal/endpoints/token/mytoken/pkg/pollingCodeRequest.go b/internal/endpoints/token/mytoken/pkg/pollingCodeRequest.go index 95e670b0..7642122f 100644 --- a/internal/endpoints/token/mytoken/pkg/pollingCodeRequest.go +++ b/internal/endpoints/token/mytoken/pkg/pollingCodeRequest.go @@ -10,3 +10,10 @@ type PollingCodeRequest struct { api.PollingCodeRequest `json:",inline"` GrantType model.GrantType `json:"grant_type"` } + +// NewPollingCodeRequest returns a new PollingCodeRequest +func NewPollingCodeRequest() PollingCodeRequest { + return PollingCodeRequest{ + GrantType: -1, // This value will remain if grant_type is not contained in the request. We have to set it to -1, because the default of 0 would be a valid GrantType + } +} diff --git a/internal/endpoints/token/mytoken/pkg/transferCodeRequest.go b/internal/endpoints/token/mytoken/pkg/transferCodeRequest.go index 351c13e4..4ed82d25 100644 --- a/internal/endpoints/token/mytoken/pkg/transferCodeRequest.go +++ b/internal/endpoints/token/mytoken/pkg/transferCodeRequest.go @@ -10,3 +10,10 @@ type ExchangeTransferCodeRequest struct { api.ExchangeTransferCodeRequest `json:",inline"` GrantType model.GrantType `json:"grant_type"` } + +// NewExchangeTransferCodeRequest returns a new ExchangeTransferCodeRequest +func NewExchangeTransferCodeRequest() ExchangeTransferCodeRequest { + return ExchangeTransferCodeRequest{ + GrantType: -1, // This value will remain if grant_type is not contained in the request. We have to set it to -1, because the default of 0 would be a valid GrantType + } +} diff --git a/internal/endpoints/token/mytoken/polling/pollingEndpoint.go b/internal/endpoints/token/mytoken/polling/pollingEndpoint.go index f183d8e0..191de7ed 100644 --- a/internal/endpoints/token/mytoken/polling/pollingEndpoint.go +++ b/internal/endpoints/token/mytoken/polling/pollingEndpoint.go @@ -16,7 +16,7 @@ import ( // HandlePollingCode handles a request on the polling endpoint func HandlePollingCode(ctx *fiber.Ctx) error { - req := response.PollingCodeRequest{} + req := response.NewPollingCodeRequest() if err := json.Unmarshal(ctx.Body(), &req); err != nil { return model.ErrorToBadRequestErrorResponse(err).Send(ctx) } diff --git a/internal/endpoints/tokeninfo/history.go b/internal/endpoints/tokeninfo/history.go index b302504b..268c6ee7 100644 --- a/internal/endpoints/tokeninfo/history.go +++ b/internal/endpoints/tokeninfo/history.go @@ -6,19 +6,21 @@ import ( "github.com/gofiber/fiber/v2" "github.com/jmoiron/sqlx" - "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/db" "github.com/oidc-mytoken/server/internal/db/dbrepo/eventrepo" + response "github.com/oidc-mytoken/server/internal/endpoints/token/mytoken/pkg" "github.com/oidc-mytoken/server/internal/endpoints/tokeninfo/pkg" "github.com/oidc-mytoken/server/internal/model" + "github.com/oidc-mytoken/server/internal/utils/cookies" eventService "github.com/oidc-mytoken/server/shared/mytoken/event" event "github.com/oidc-mytoken/server/shared/mytoken/event/pkg" mytoken "github.com/oidc-mytoken/server/shared/mytoken/pkg" "github.com/oidc-mytoken/server/shared/mytoken/restrictions" + "github.com/oidc-mytoken/server/shared/mytoken/rotation" ) -func handleTokenInfoHistory(mt *mytoken.Mytoken, clientMetadata *api.ClientMetaData) model.Response { +func handleTokenInfoHistory(req pkg.TokenInfoRequest, mt *mytoken.Mytoken, clientMetadata *api.ClientMetaData) model.Response { // If we call this function it means the token is valid. if !mt.Capabilities.Has(api.CapabilityTokeninfoHistory) { @@ -41,6 +43,7 @@ func handleTokenInfoHistory(mt *mytoken.Mytoken, clientMetadata *api.ClientMetaD } var history eventrepo.EventHistory + var tokenUpdate *response.MytokenResponse if err := db.Transact(func(tx *sqlx.Tx) error { var err error history, err = eventrepo.GetEventHistory(tx, mt.ID) @@ -53,6 +56,10 @@ func handleTokenInfoHistory(mt *mytoken.Mytoken, clientMetadata *api.ClientMetaD if err = usedRestriction.UsedOther(tx, mt.ID); err != nil { return err } + tokenUpdate, err = rotation.RotateMytokenAfterOtherForResponse(tx, req.Mytoken.JWT, mt, *clientMetadata, req.Mytoken.OriginalTokenType) + if err != nil { + return err + } return eventService.LogEvent(tx, eventService.MTEvent{ Event: event.FromNumber(event.MTEventTokenInfoHistory, ""), MTID: mt.ID, @@ -60,8 +67,16 @@ func handleTokenInfoHistory(mt *mytoken.Mytoken, clientMetadata *api.ClientMetaD }); err != nil { return *model.ErrorToInternalServerErrorResponse(err) } + rsp := pkg.NewTokeninfoHistoryResponse(history) + var cake []*fiber.Cookie + if tokenUpdate != nil { + rsp.TokenUpdate = tokenUpdate + cookie := cookies.MytokenCookie(tokenUpdate.Mytoken) + cake = []*fiber.Cookie{&cookie} + } return model.Response{ Status: fiber.StatusOK, - Response: pkg.NewTokeninfoHistoryResponse(history), + Response: rsp, + Cookies: cake, } } diff --git a/internal/endpoints/tokeninfo/introspect.go b/internal/endpoints/tokeninfo/introspect.go index 0b34b480..813c200f 100644 --- a/internal/endpoints/tokeninfo/introspect.go +++ b/internal/endpoints/tokeninfo/introspect.go @@ -13,7 +13,7 @@ import ( mytoken "github.com/oidc-mytoken/server/shared/mytoken/pkg" ) -func handleTokenInfoIntrospect(mt *mytoken.Mytoken, clientMetadata *api.ClientMetaData) model.Response { +func handleTokenInfoIntrospect(req pkg.TokenInfoRequest, mt *mytoken.Mytoken, clientMetadata *api.ClientMetaData) model.Response { // If we call this function it means the token is valid. if !mt.Capabilities.Has(api.CapabilityTokeninfoIntrospect) { diff --git a/internal/endpoints/tokeninfo/list.go b/internal/endpoints/tokeninfo/list.go index 970dc84b..2f147235 100644 --- a/internal/endpoints/tokeninfo/list.go +++ b/internal/endpoints/tokeninfo/list.go @@ -6,6 +6,9 @@ import ( "github.com/gofiber/fiber/v2" "github.com/jmoiron/sqlx" + response "github.com/oidc-mytoken/server/internal/endpoints/token/mytoken/pkg" + "github.com/oidc-mytoken/server/internal/utils/cookies" + "github.com/oidc-mytoken/server/shared/mytoken/rotation" "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/db" @@ -18,7 +21,7 @@ import ( "github.com/oidc-mytoken/server/shared/mytoken/restrictions" ) -func handleTokenInfoList(mt *mytoken.Mytoken, clientMetadata *api.ClientMetaData) model.Response { +func handleTokenInfoList(req pkg.TokenInfoRequest, mt *mytoken.Mytoken, clientMetadata *api.ClientMetaData) model.Response { // If we call this function it means the token is valid. if !mt.Capabilities.Has(api.CapabilityListMT) { @@ -41,6 +44,7 @@ func handleTokenInfoList(mt *mytoken.Mytoken, clientMetadata *api.ClientMetaData } var tokenList []tree.MytokenEntryTree + var tokenUpdate *response.MytokenResponse if err := db.Transact(func(tx *sqlx.Tx) error { var err error tokenList, err = tree.AllTokens(tx, mt.ID) @@ -53,6 +57,10 @@ func handleTokenInfoList(mt *mytoken.Mytoken, clientMetadata *api.ClientMetaData if err = usedRestriction.UsedOther(tx, mt.ID); err != nil { return err } + tokenUpdate, err = rotation.RotateMytokenAfterOtherForResponse(tx, req.Mytoken.JWT, mt, *clientMetadata, req.Mytoken.OriginalTokenType) + if err != nil { + return err + } return eventService.LogEvent(tx, eventService.MTEvent{ Event: event.FromNumber(event.MTEventTokenInfoListMTs, ""), MTID: mt.ID, @@ -61,8 +69,16 @@ func handleTokenInfoList(mt *mytoken.Mytoken, clientMetadata *api.ClientMetaData return *model.ErrorToInternalServerErrorResponse(err) } + rsp := pkg.NewTokeninfoListResponse(tokenList) + var cake []*fiber.Cookie + if tokenUpdate != nil { + rsp.TokenUpdate = tokenUpdate + cookie := cookies.MytokenCookie(tokenUpdate.Mytoken) + cake = []*fiber.Cookie{&cookie} + } return model.Response{ Status: fiber.StatusOK, - Response: pkg.NewTokeninfoListResponse(tokenList), + Response: rsp, + Cookies: cake, } } diff --git a/internal/endpoints/tokeninfo/pkg/tokeninfoHistoryResponse.go b/internal/endpoints/tokeninfo/pkg/tokeninfoHistoryResponse.go index 442bea29..edd0f7cb 100644 --- a/internal/endpoints/tokeninfo/pkg/tokeninfoHistoryResponse.go +++ b/internal/endpoints/tokeninfo/pkg/tokeninfoHistoryResponse.go @@ -2,12 +2,14 @@ package pkg import ( "github.com/oidc-mytoken/server/internal/db/dbrepo/eventrepo" + my "github.com/oidc-mytoken/server/internal/endpoints/token/mytoken/pkg" ) // TokeninfoHistoryResponse is type for responses to tokeninfo history requests type TokeninfoHistoryResponse struct { // un update check api.TokeninfoHistoryResponse EventHistory eventrepo.EventHistory `json:"events"` + TokenUpdate *my.MytokenResponse `json:"token_update,omitempty"` } // NewTokeninfoHistoryResponse creates a new TokeninfoHistoryResponse diff --git a/internal/endpoints/tokeninfo/pkg/tokeninfoListResponse.go b/internal/endpoints/tokeninfo/pkg/tokeninfoListResponse.go index b30ee4eb..965af3f4 100644 --- a/internal/endpoints/tokeninfo/pkg/tokeninfoListResponse.go +++ b/internal/endpoints/tokeninfo/pkg/tokeninfoListResponse.go @@ -2,12 +2,14 @@ package pkg import ( "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/tree" + my "github.com/oidc-mytoken/server/internal/endpoints/token/mytoken/pkg" ) // TokeninfoListResponse is type for responses to tokeninfo list requests type TokeninfoListResponse struct { // un update check api.TokeninfoListResponse - Tokens []tree.MytokenEntryTree `json:"mytokens"` + Tokens []tree.MytokenEntryTree `json:"mytokens"` + TokenUpdate *my.MytokenResponse `json:"token_update,omitempty"` } // NewTokeninfoListResponse creates a new TokeninfoListResponse diff --git a/internal/endpoints/tokeninfo/pkg/tokeninfoRequest.go b/internal/endpoints/tokeninfo/pkg/tokeninfoRequest.go index a4b040a9..4ed78c1a 100644 --- a/internal/endpoints/tokeninfo/pkg/tokeninfoRequest.go +++ b/internal/endpoints/tokeninfo/pkg/tokeninfoRequest.go @@ -3,12 +3,12 @@ package pkg import ( "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/shared/model" - "github.com/oidc-mytoken/server/shared/mytoken/token" + "github.com/oidc-mytoken/server/shared/mytoken/universalmytoken" ) // TokenInfoRequest is a type for holding a request to the tokeninfo endpoint type TokenInfoRequest struct { api.TokenInfoRequest `json:",inline"` - Action model.TokeninfoAction `json:"action"` - Mytoken token.Token `json:"mytoken"` + Action model.TokeninfoAction `json:"action"` + Mytoken universalmytoken.UniversalMytoken `json:"mytoken"` } diff --git a/internal/endpoints/tokeninfo/pkg/tokeninfoTreeResponse.go b/internal/endpoints/tokeninfo/pkg/tokeninfoTreeResponse.go index eff9b820..c6907e19 100644 --- a/internal/endpoints/tokeninfo/pkg/tokeninfoTreeResponse.go +++ b/internal/endpoints/tokeninfo/pkg/tokeninfoTreeResponse.go @@ -2,12 +2,14 @@ package pkg import ( "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/tree" + my "github.com/oidc-mytoken/server/internal/endpoints/token/mytoken/pkg" ) // TokeninfoTreeResponse is type for responses to tokeninfo tree requests type TokeninfoTreeResponse struct { - // un update check api.TokeninforTeeResponse - Tokens tree.MytokenEntryTree `json:"mytokens"` + // on update check api.TokeninfoTreeResponse + Tokens tree.MytokenEntryTree `json:"mytokens"` + TokenUpdate *my.MytokenResponse `json:"token_update,omitempty"` } // NewTokeninfoTreeResponse creates a new TokeninfoTreeResponse diff --git a/internal/endpoints/tokeninfo/tokeninfo.go b/internal/endpoints/tokeninfo/tokeninfo.go index aa8d552b..1ce4ba9b 100644 --- a/internal/endpoints/tokeninfo/tokeninfo.go +++ b/internal/endpoints/tokeninfo/tokeninfo.go @@ -20,20 +20,20 @@ func HandleTokenInfo(ctx *fiber.Ctx) error { if err := json.Unmarshal(ctx.Body(), &req); err != nil { return model.ErrorToBadRequestErrorResponse(err).Send(ctx) } - st, errRes := testMytoken(ctx, &req) + mt, errRes := testMytoken(ctx, &req) if errRes != nil { return errRes.Send(ctx) } clientMetadata := ctxUtils.ClientMetaData(ctx) switch req.Action { case model2.TokeninfoActionIntrospect: - return handleTokenInfoIntrospect(st, clientMetadata).Send(ctx) + return handleTokenInfoIntrospect(req, mt, clientMetadata).Send(ctx) case model2.TokeninfoActionEventHistory: - return handleTokenInfoHistory(st, clientMetadata).Send(ctx) + return handleTokenInfoHistory(req, mt, clientMetadata).Send(ctx) case model2.TokeninfoActionSubtokenTree: - return handleTokenInfoTree(st, clientMetadata).Send(ctx) + return handleTokenInfoTree(req, mt, clientMetadata).Send(ctx) case model2.TokeninfoActionListMytokens: - return handleTokenInfoList(st, clientMetadata).Send(ctx) + return handleTokenInfoList(req, mt, clientMetadata).Send(ctx) default: return model.Response{ Status: fiber.StatusBadRequest, @@ -43,7 +43,7 @@ func HandleTokenInfo(ctx *fiber.Ctx) error { } func testMytoken(ctx *fiber.Ctx, req *pkg.TokenInfoRequest) (*mytoken.Mytoken, *model.Response) { - if req.Mytoken == "" { + if req.Mytoken.JWT == "" { if t := ctxUtils.GetMytoken(ctx); t != nil { req.Mytoken = *t } else { @@ -54,7 +54,7 @@ func testMytoken(ctx *fiber.Ctx, req *pkg.TokenInfoRequest) (*mytoken.Mytoken, * } } - mt, err := mytoken.ParseJWT(string(req.Mytoken)) + mt, err := mytoken.ParseJWT(req.Mytoken.JWT) if err != nil { return nil, &model.Response{ Status: fiber.StatusUnauthorized, diff --git a/internal/endpoints/tokeninfo/tree.go b/internal/endpoints/tokeninfo/tree.go index b02909fc..f283af91 100644 --- a/internal/endpoints/tokeninfo/tree.go +++ b/internal/endpoints/tokeninfo/tree.go @@ -6,6 +6,9 @@ import ( "github.com/gofiber/fiber/v2" "github.com/jmoiron/sqlx" + response "github.com/oidc-mytoken/server/internal/endpoints/token/mytoken/pkg" + "github.com/oidc-mytoken/server/internal/utils/cookies" + "github.com/oidc-mytoken/server/shared/mytoken/rotation" "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/db" @@ -18,7 +21,7 @@ import ( "github.com/oidc-mytoken/server/shared/mytoken/restrictions" ) -func handleTokenInfoTree(mt *mytoken.Mytoken, clientMetadata *api.ClientMetaData) model.Response { +func handleTokenInfoTree(req pkg.TokenInfoRequest, mt *mytoken.Mytoken, clientMetadata *api.ClientMetaData) model.Response { // If we call this function it means the token is valid. if !mt.Capabilities.Has(api.CapabilityTokeninfoTree) { @@ -41,6 +44,7 @@ func handleTokenInfoTree(mt *mytoken.Mytoken, clientMetadata *api.ClientMetaData } var tokenTree tree.MytokenEntryTree + var tokenUpdate *response.MytokenResponse if err := db.Transact(func(tx *sqlx.Tx) error { var err error tokenTree, err = tree.TokenSubTree(tx, mt.ID) @@ -53,6 +57,10 @@ func handleTokenInfoTree(mt *mytoken.Mytoken, clientMetadata *api.ClientMetaData if err = usedRestriction.UsedOther(tx, mt.ID); err != nil { return err } + tokenUpdate, err = rotation.RotateMytokenAfterOtherForResponse(tx, req.Mytoken.JWT, mt, *clientMetadata, req.Mytoken.OriginalTokenType) + if err != nil { + return err + } return eventService.LogEvent(tx, eventService.MTEvent{ Event: event.FromNumber(event.MTEventTokenInfoTree, ""), MTID: mt.ID, @@ -61,8 +69,16 @@ func handleTokenInfoTree(mt *mytoken.Mytoken, clientMetadata *api.ClientMetaData return *model.ErrorToInternalServerErrorResponse(err) } + rsp := pkg.NewTokeninfoTreeResponse(tokenTree) + var cake []*fiber.Cookie + if tokenUpdate != nil { + rsp.TokenUpdate = tokenUpdate + cookie := cookies.MytokenCookie(tokenUpdate.Mytoken) + cake = []*fiber.Cookie{&cookie} + } return model.Response{ Status: fiber.StatusOK, - Response: pkg.NewTokeninfoTreeResponse(tokenTree), + Response: rsp, + Cookies: cake, } } diff --git a/internal/oidc/authcode/authcode.go b/internal/oidc/authcode/authcode.go index 95f419b1..2fb28727 100644 --- a/internal/oidc/authcode/authcode.go +++ b/internal/oidc/authcode/authcode.go @@ -9,6 +9,7 @@ import ( "github.com/coreos/go-oidc/v3/oidc" "github.com/gofiber/fiber/v2" "github.com/jmoiron/sqlx" + "github.com/oidc-mytoken/server/internal/utils/cookies" log "github.com/sirupsen/logrus" "golang.org/x/oauth2" @@ -101,6 +102,7 @@ func StartAuthCodeFlow(ctx *fiber.Ctx, oidcReq response.OIDCFlowRequest) *model. Capabilities: req.Capabilities, SubtokenCapabilities: req.SubtokenCapabilities, Name: req.Name, + Rotation: req.Rotation, } authFlowInfo := authcodeinforepo.AuthFlowInfo{ AuthFlowInfoOut: authFlowInfoO, @@ -230,26 +232,16 @@ func CodeExchange(oState *state.State, code string, networkData api.ClientMetaDa if err != nil { return model.ErrorToInternalServerErrorResponse(err) } - cookieName := "mytoken" - cookieValue := res.Mytoken - cookieAge := 3600 * 24 //TODO from config, same as in js + var cookie fiber.Cookie if stateInf.ResponseType == pkgModel.ResponseTypeTransferCode { - cookieName = "mytoken-transfercode" - cookieValue = res.TransferCode - cookieAge = int(res.ExpiresIn) + cookie = cookies.TransferCodeCookie(res.TransferCode, int(res.ExpiresIn)) + } else { + cookie = cookies.MytokenCookie(res.Mytoken) } return &model.Response{ Status: fiber.StatusSeeOther, Response: "/home", - Cookies: []*fiber.Cookie{{ - Name: cookieName, - Value: cookieValue, - Path: "/api", - MaxAge: cookieAge, - Secure: config.Get().Server.Secure, - HTTPOnly: true, - SameSite: "Strict", - }}, + Cookies: []*fiber.Cookie{&cookie}, } } @@ -260,7 +252,8 @@ func createMytokenEntry(tx *sqlx.Tx, authFlowInfo *authcodeinforepo.AuthFlowInfo authFlowInfo.Issuer, authFlowInfo.Restrictions, authFlowInfo.Capabilities, - authFlowInfo.SubtokenCapabilities), + authFlowInfo.SubtokenCapabilities, + authFlowInfo.Rotation), authFlowInfo.Name, networkData) if err := ste.InitRefreshToken(token.RefreshToken); err != nil { return nil, err diff --git a/internal/server/web/static/js/login.js b/internal/server/web/static/js/login.js index 6ac37d46..1bf8be7d 100644 --- a/internal/server/web/static/js/login.js +++ b/internal/server/web/static/js/login.js @@ -6,7 +6,7 @@ $('#login-form').on('submit', function(e){ let data = $(this).serializeObject() data['restrictions'] = [ { - "exp": Math.floor(Date.now() / 1000) + 3600*24, // TODO configurable + "exp": Math.floor(Date.now() / 1000) + 3600*24*7, // TODO configurable "ip": ["this"], "usages_AT": 1, "usages_other": 50, @@ -23,6 +23,10 @@ $('#login-form').on('submit', function(e){ // "settings", "list_mytokens" ] + data['rotation'] = { + "on_other": true, + "lifetime": 3600*24, + } data['name'] = "mytoken-web"; data = JSON.stringify(data); $.ajax({ diff --git a/internal/utils/cookies/cookies.go b/internal/utils/cookies/cookies.go new file mode 100644 index 00000000..e6072104 --- /dev/null +++ b/internal/utils/cookies/cookies.go @@ -0,0 +1,38 @@ +package cookies + +import ( + "github.com/gofiber/fiber/v2" + "github.com/oidc-mytoken/server/internal/config" +) + +const ( + cookieAge = 3600 * 24 * 7 //TODO from config, same as in js + mytokenCookieName = "mytoken" + transferCodeCookieName = "mytoken-transfercode" +) + +// MytokenCookie creates a fiber.Cookie for the passed mytoken +func MytokenCookie(mytoken string) fiber.Cookie { + return fiber.Cookie{ + Name: mytokenCookieName, + Value: mytoken, + Path: "/api", + MaxAge: cookieAge, + Secure: config.Get().Server.Secure, + HTTPOnly: true, + SameSite: "Strict", + } +} + +// TransferCodeCookie creates a fiber.Cookie for the passed transfer code +func TransferCodeCookie(transferCode string, expiresIn int) fiber.Cookie { + return fiber.Cookie{ + Name: transferCodeCookieName, + Value: transferCode, + Path: "/api", + MaxAge: expiresIn, + Secure: config.Get().Server.Secure, + HTTPOnly: true, + SameSite: "Strict", + } +} diff --git a/internal/utils/ctxUtils/token.go b/internal/utils/ctxUtils/token.go index 3a2cb0d4..d0c57f95 100644 --- a/internal/utils/ctxUtils/token.go +++ b/internal/utils/ctxUtils/token.go @@ -6,7 +6,7 @@ import ( "github.com/gofiber/fiber/v2" "github.com/oidc-mytoken/api/v0" - "github.com/oidc-mytoken/server/shared/mytoken/token" + "github.com/oidc-mytoken/server/shared/mytoken/universalmytoken" ) // GetMytokenStr checks a fiber.Ctx for a mytoken and returns the token string as passed to the request @@ -27,12 +27,12 @@ func GetMytokenStr(ctx *fiber.Ctx) string { } // GetMytoken checks a fiber.Ctx for a mytoken and returns a token object -func GetMytoken(ctx *fiber.Ctx) *token.Token { +func GetMytoken(ctx *fiber.Ctx) *universalmytoken.UniversalMytoken { tok := GetMytokenStr(ctx) if tok == "" { return nil } - t, err := token.GetLongMytoken(tok) + t, err := universalmytoken.Parse(tok) if err != nil { return nil } diff --git a/shared/mytoken/event/pkg/event.go b/shared/mytoken/event/pkg/event.go index 1ac12bd7..28c5b5ed 100644 --- a/shared/mytoken/event/pkg/event.go +++ b/shared/mytoken/event/pkg/event.go @@ -62,7 +62,28 @@ func eventStringToInt(str string) int { } // AllEvents hold all possible Events -var AllEvents = [...]string{"unknown", "created", "AT_created", "MT_created", "tokeninfo_introspect", "tokeninfo_history", "tokeninfo_tree", "tokeninfo_list_mytokens", "mng_enabled_AT_grant", "mng_disabled_AT_grant", "mng_enabled_JWT_grant", "mng_disabled_JWT_grant", "mng_linked_grant", "mng_unlinked_grant", "mng_enabled_tracing", "mng_disabled_tracing", "inherited_RT", "transfer_code_created", "transfer_code_used"} +var AllEvents = [...]string{ + "unknown", + "created", + "AT_created", + "MT_created", + "tokeninfo_introspect", + "tokeninfo_history", + "tokeninfo_tree", + "tokeninfo_list_mytokens", + "mng_enabled_AT_grant", + "mng_disabled_AT_grant", + "mng_enabled_JWT_grant", + "mng_disabled_JWT_grant", + "mng_linked_grant", + "mng_unlinked_grant", + "mng_enabled_tracing", + "mng_disabled_tracing", + "inherited_RT", + "transfer_code_created", + "transfer_code_used", + "token_rotated", +} // Events for Mytokens const ( @@ -85,5 +106,6 @@ const ( MTEventInheritedRT MTEventTransferCodeCreated MTEventTransferCodeUsed + MTEventTokenRotated maxEvent ) diff --git a/shared/mytoken/mytokenHandler.go b/shared/mytoken/mytokenHandler.go index 6a1af57c..358e9f5e 100644 --- a/shared/mytoken/mytokenHandler.go +++ b/shared/mytoken/mytokenHandler.go @@ -9,6 +9,8 @@ import ( "github.com/gofiber/fiber/v2" "github.com/jmoiron/sqlx" + "github.com/oidc-mytoken/server/internal/utils/cookies" + "github.com/oidc-mytoken/server/shared/mytoken/rotation" log "github.com/sirupsen/logrus" "github.com/oidc-mytoken/api/v0" @@ -29,14 +31,13 @@ import ( mytoken "github.com/oidc-mytoken/server/shared/mytoken/pkg" "github.com/oidc-mytoken/server/shared/mytoken/pkg/mtid" "github.com/oidc-mytoken/server/shared/mytoken/restrictions" - "github.com/oidc-mytoken/server/shared/mytoken/token" - "github.com/oidc-mytoken/server/shared/utils" + "github.com/oidc-mytoken/server/shared/mytoken/universalmytoken" ) // HandleMytokenFromTransferCode handles requests to return the mytoken for a transfer code func HandleMytokenFromTransferCode(ctx *fiber.Ctx) *model.Response { log.Debug("Handle mytoken from transfercode") - req := response.ExchangeTransferCodeRequest{} + req := response.NewExchangeTransferCodeRequest() if err := json.Unmarshal(ctx.Body(), &req); err != nil { return model.ErrorToBadRequestErrorResponse(err) } @@ -71,15 +72,11 @@ func HandleMytokenFromTransferCode(ctx *fiber.Ctx) *model.Response { return model.ErrorToInternalServerErrorResponse(err) } - tokenType := pkgModel.ResponseTypeToken - if !utils.IsJWT(tokenStr) { - tokenType = pkgModel.ResponseTypeShortToken - } - jwt, err := token.GetLongMytoken(tokenStr) + token, err := universalmytoken.Parse(tokenStr) if err != nil { return model.ErrorToInternalServerErrorResponse(err) } - mt, err := mytoken.ParseJWT(string(jwt)) + mt, err := mytoken.ParseJWT(token.JWT) if err != nil { return model.ErrorToInternalServerErrorResponse(err) } @@ -87,12 +84,12 @@ func HandleMytokenFromTransferCode(ctx *fiber.Ctx) *model.Response { Status: fiber.StatusOK, Response: response.MytokenResponse{ MytokenResponse: api.MytokenResponse{ - Mytoken: tokenStr, + Mytoken: token.OriginalToken, ExpiresIn: mt.ExpiresIn(), Capabilities: mt.Capabilities, SubtokenCapabilities: mt.SubtokenCapabilities, }, - MytokenType: tokenType, + MytokenType: token.OriginalTokenType, Restrictions: mt.Restrictions, }, } @@ -112,9 +109,9 @@ func HandleMytokenFromMytoken(ctx *fiber.Ctx) *model.Response { // GrantType already checked - if len(req.Mytoken) == 0 { + if req.Mytoken.JWT == "" { var err error - req.Mytoken, err = token.GetLongMytoken(ctx.Cookies("mytoken")) + req.Mytoken, err = universalmytoken.Parse(ctx.Cookies("mytoken")) if err != nil { return &model.Response{ Status: fiber.StatusUnauthorized, @@ -123,7 +120,7 @@ func HandleMytokenFromMytoken(ctx *fiber.Ctx) *model.Response { } } - mt, err := mytoken.ParseJWT(string(req.Mytoken)) + mt, err := mytoken.ParseJWT(req.Mytoken.JWT) if err != nil { return &model.Response{ Status: fiber.StatusUnauthorized, @@ -178,14 +175,19 @@ func handleMytokenFromMytoken(parent *mytoken.Mytoken, req *response.MytokenFrom if errorResponse != nil { return errorResponse } - if err := db.Transact(func(tx *sqlx.Tx) error { + var tokenUpdate *response.MytokenResponse + if err := db.Transact(func(tx *sqlx.Tx) (err error) { if len(parent.Restrictions) > 0 { - if err := parent.Restrictions.GetValidForOther(tx, networkData.IP, parent.ID)[0].UsedOther(tx, parent.ID); err != nil { - return err + if err = parent.Restrictions.GetValidForOther(tx, networkData.IP, parent.ID)[0].UsedOther(tx, parent.ID); err != nil { + return } } - if err := ste.Store(tx, "Used grant_type mytoken"); err != nil { - return err + tokenUpdate, err = rotation.RotateMytokenAfterOtherForResponse(tx, req.Mytoken.JWT, parent, *networkData, req.Mytoken.OriginalTokenType) + if err != nil { + return + } + if err = ste.Store(tx, "Used grant_type mytoken"); err != nil { + return } return eventService.LogEvents(tx, []eventService.MTEvent{ {event.FromNumber(event.MTEventInheritedRT, "Got RT from parent"), ste.ID}, @@ -199,9 +201,16 @@ func handleMytokenFromMytoken(parent *mytoken.Mytoken, req *response.MytokenFrom if err != nil { return model.ErrorToInternalServerErrorResponse(err) } + var cake []*fiber.Cookie + if tokenUpdate != nil { + res.TokenUpdate = tokenUpdate + cookie := cookies.MytokenCookie(tokenUpdate.Mytoken) + cake = []*fiber.Cookie{&cookie} + } return &model.Response{ Status: fiber.StatusOK, Response: res, + Cookies: cake, } } @@ -209,6 +218,7 @@ func createMytokenEntry(parent *mytoken.Mytoken, req *response.MytokenFromMytoke rtID, dbErr := refreshtokenrepo.GetRTID(nil, parent.ID) rtFound, err := dbhelper.ParseError(dbErr) if err != nil { + log.WithError(dbErr).Error() return nil, model.ErrorToInternalServerErrorResponse(dbErr) } if !rtFound { @@ -219,6 +229,7 @@ func createMytokenEntry(parent *mytoken.Mytoken, req *response.MytokenFromMytoke } rootID, rootFound, dbErr := dbhelper.GetMTRootID(parent.ID) if dbErr != nil { + log.WithError(dbErr).Error() return nil, model.ErrorToInternalServerErrorResponse(dbErr) } if !rootFound { @@ -259,13 +270,15 @@ func createMytokenEntry(parent *mytoken.Mytoken, req *response.MytokenFromMytoke sc = api.Tighten(capsFromParent, req.SubtokenCapabilities) } ste := mytokenrepo.NewMytokenEntry( - mytoken.NewMytoken(parent.OIDCSubject, parent.OIDCIssuer, r, c, sc), + mytoken.NewMytoken(parent.OIDCSubject, parent.OIDCIssuer, r, c, sc, req.Rotation), req.Name, networkData) - encryptionKey, _, err := refreshtokenrepo.GetEncryptionKey(nil, parent.ID, string(req.Mytoken)) + encryptionKey, _, err := refreshtokenrepo.GetEncryptionKey(nil, parent.ID, req.Mytoken.JWT) if err != nil { + log.WithError(err).Error() return ste, model.ErrorToInternalServerErrorResponse(err) } if err = ste.SetRefreshToken(rtID, encryptionKey); err != nil { + log.WithError(err).Error() return ste, model.ErrorToInternalServerErrorResponse(err) } ste.ParentID = parent.ID @@ -274,7 +287,7 @@ func createMytokenEntry(parent *mytoken.Mytoken, req *response.MytokenFromMytoke } // RevokeMytoken revokes a Mytoken -func RevokeMytoken(tx *sqlx.Tx, id mtid.MTID, token token.Token, recursive bool, issuer string) *model.Response { +func RevokeMytoken(tx *sqlx.Tx, id mtid.MTID, jwt string, recursive bool, issuer string) *model.Response { provider, ok := config.Get().ProviderByIssuer[issuer] if !ok { return &model.Response{ @@ -290,7 +303,7 @@ func RevokeMytoken(tx *sqlx.Tx, id mtid.MTID, token token.Token, recursive bool, } return err } - rt, _, err := refreshtokenrepo.GetRefreshToken(tx, id, string(token)) + rt, _, err := refreshtokenrepo.GetRefreshToken(tx, id, jwt) if err != nil { return err } diff --git a/shared/mytoken/pkg/mytoken.go b/shared/mytoken/pkg/mytoken.go index a846c073..35024ade 100644 --- a/shared/mytoken/pkg/mytoken.go +++ b/shared/mytoken/pkg/mytoken.go @@ -17,7 +17,6 @@ import ( event "github.com/oidc-mytoken/server/shared/mytoken/event/pkg" "github.com/oidc-mytoken/server/shared/mytoken/pkg/mtid" "github.com/oidc-mytoken/server/shared/mytoken/restrictions" - "github.com/oidc-mytoken/server/shared/mytoken/rotation" "github.com/oidc-mytoken/server/shared/utils/issuerUtils" "github.com/oidc-mytoken/server/shared/utils/unixtime" ) @@ -40,10 +39,22 @@ type Mytoken struct { Restrictions restrictions.Restrictions `json:"restrictions,omitempty"` Capabilities api.Capabilities `json:"capabilities"` SubtokenCapabilities api.Capabilities `json:"subtoken_capabilities,omitempty"` - Rotation *rotation.Rotation `json:"rotation,omitempty"` + Rotation *api.Rotation `json:"rotation,omitempty"` jwt string } +func (mt Mytoken) Rotate() *Mytoken { + rotated := mt + rotated.SeqNo++ + if rotated.Rotation.Lifetime > 0 { + rotated.ExpiresAt = unixtime.InSeconds(int64(rotated.Rotation.Lifetime)) + } + rotated.IssuedAt = unixtime.Now() + rotated.NotBefore = rotated.IssuedAt + rotated.jwt = "" + return &rotated +} + func (mt *Mytoken) verifyID() bool { return mt.ID.Valid() } @@ -72,7 +83,7 @@ func (mt *Mytoken) VerifyCapabilities(required ...api.Capability) bool { } // NewMytoken creates a new Mytoken -func NewMytoken(oidcSub, oidcIss string, r restrictions.Restrictions, c, sc api.Capabilities) *Mytoken { +func NewMytoken(oidcSub, oidcIss string, r restrictions.Restrictions, c, sc api.Capabilities, rot *api.Rotation) *Mytoken { now := unixtime.Now() mt := &Mytoken{ Version: api.TokenVer, @@ -88,6 +99,7 @@ func NewMytoken(oidcSub, oidcIss string, r restrictions.Restrictions, c, sc api. OIDCSubject: oidcSub, Capabilities: c, SubtokenCapabilities: sc, + Rotation: rot, } r.EnforceMaxLifetime(oidcIss) if len(r) > 0 { @@ -171,6 +183,7 @@ func (mt *Mytoken) toTokenResponse() response.MytokenResponse { ExpiresIn: mt.ExpiresIn(), Capabilities: mt.Capabilities, SubtokenCapabilities: mt.SubtokenCapabilities, + Rotation: mt.Rotation, }, Restrictions: mt.Restrictions, } @@ -235,8 +248,9 @@ func ParseJWT(token string) (*Mytoken, error) { return nil, err } - if st, ok := tok.Claims.(*Mytoken); ok && tok.Valid { - return st, nil + if mt, ok := tok.Claims.(*Mytoken); ok && tok.Valid { + mt.jwt = token + return mt, nil } return nil, fmt.Errorf("token not valid") } diff --git a/shared/mytoken/restrictions/restriction.go b/shared/mytoken/restrictions/restriction.go index 60969228..71590fa8 100644 --- a/shared/mytoken/restrictions/restriction.go +++ b/shared/mytoken/restrictions/restriction.go @@ -429,7 +429,7 @@ func Tighten(old, wanted Restrictions) (res Restrictions, ok bool) { return } base := Restrictions{} - if err := copier.Copy(&base, &old); err != nil { + if err := copier.CopyWithOption(&base, &old, copier.Option{DeepCopy: true}); err != nil { log.WithError(err).Error() } var droppedRestrictionsFromWanted bool diff --git a/shared/mytoken/rotation/rotation.go b/shared/mytoken/rotation/rotation.go index 08b82f7b..4e19fb08 100644 --- a/shared/mytoken/rotation/rotation.go +++ b/shared/mytoken/rotation/rotation.go @@ -1,8 +1,85 @@ package rotation import ( + "github.com/jmoiron/sqlx" "github.com/oidc-mytoken/api/v0" + "github.com/oidc-mytoken/server/internal/db" + helper "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/mytokenrepohelper" + "github.com/oidc-mytoken/server/internal/db/dbrepo/refreshtokenrepo" + "github.com/oidc-mytoken/server/internal/endpoints/token/mytoken/pkg" + "github.com/oidc-mytoken/server/shared/model" + eventService "github.com/oidc-mytoken/server/shared/mytoken/event" + event "github.com/oidc-mytoken/server/shared/mytoken/event/pkg" + mytoken "github.com/oidc-mytoken/server/shared/mytoken/pkg" ) -// Rotation is a type for specifying the rotation of mytokens -type Rotation api.Rotation +func rotateMytoken(tx *sqlx.Tx, oldJWT string, old *mytoken.Mytoken, clientMetaData api.ClientMetaData) (*mytoken.Mytoken, bool, error) { + rotated := old.Rotate() + jwt, err := rotated.ToJWT() + if err != nil { + return old, false, err + } + if err = db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { + if err = helper.UpdateSeqNo(tx, rotated.ID, rotated.SeqNo); err != nil { + return err + } + if err = refreshtokenrepo.ReencryptEncryptionKey(tx, rotated.ID, oldJWT, jwt); err != nil { + return err + } + return eventService.LogEvent(tx, eventService.MTEvent{ + Event: event.FromNumber(event.MTEventTokenRotated, ""), + MTID: rotated.ID, + }, clientMetaData) + }); err != nil { + return old, false, err + } + return rotated, true, nil +} + +// RotateMytokenAfterAT rotates a mytoken after it was used to obtain an AT if rotation is enabled for that case +func RotateMytokenAfterAT(tx *sqlx.Tx, oldJWT string, old *mytoken.Mytoken, clientMetaData api.ClientMetaData) (*mytoken.Mytoken, bool, error) { + if old.Rotation == nil { + return old, false, nil + } + if !old.Rotation.OnAT { + return old, false, nil + } + return rotateMytoken(tx, oldJWT, old, clientMetaData) +} + +// RotateMytokenAfterOther rotates a mytoken after it was used for other usages than AT if rotation is enabled for that case +func RotateMytokenAfterOther(tx *sqlx.Tx, oldJWT string, old *mytoken.Mytoken, clientMetaData api.ClientMetaData) (*mytoken.Mytoken, bool, error) { + if old.Rotation == nil { + return old, false, nil + } + if !old.Rotation.OnOther { + return old, false, nil + } + return rotateMytoken(tx, oldJWT, old, clientMetaData) +} + +// RotateMytokenAfterOtherForResponse rotates a mytoken after it was used for other usages than AT if rotation is enabled for that case and returns a pkg.MytokenResponse with the updated infos +func RotateMytokenAfterOtherForResponse(tx *sqlx.Tx, oldJWT string, old *mytoken.Mytoken, clientMetaData api.ClientMetaData, responseType model.ResponseType) (*pkg.MytokenResponse, error) { + my, rotated, err := RotateMytokenAfterOther(tx, oldJWT, old, clientMetaData) + if err != nil { + return nil, err + } + if !rotated { + return nil, nil + } + resp, err := my.ToTokenResponse(responseType, clientMetaData, "") + return &resp, err +} + +// RotateMytokenAfterATForResponse rotates a mytoken after it was used for obtaining an AT if rotation is enabled for that case and returns a pkg.MytokenResponse with the updated infos +func RotateMytokenAfterATForResponse(tx *sqlx.Tx, oldJWT string, old *mytoken.Mytoken, clientMetaData api.ClientMetaData, responseType model.ResponseType) (*pkg.MytokenResponse, error) { + my, rotated, err := RotateMytokenAfterAT(tx, oldJWT, old, clientMetaData) + if err != nil { + return nil, err + } + if !rotated { + return nil, nil + } + resp, err := my.ToTokenResponse(responseType, clientMetaData, "") + return &resp, err +} diff --git a/shared/mytoken/token/token.go b/shared/mytoken/token/token.go deleted file mode 100644 index f6234484..00000000 --- a/shared/mytoken/token/token.go +++ /dev/null @@ -1,36 +0,0 @@ -package token - -import ( - "encoding/json" - "fmt" - - "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/transfercoderepo" - "github.com/oidc-mytoken/server/shared/utils" -) - -// Token is a type used for tokens passed in http requests; these can be normal Mytoken or a short token. This type will unmarshal always into a jwt of the (long) Mytoken -type Token string - -// UnmarshalJSON implements the json.Unmarshaler interface -func (t *Token) UnmarshalJSON(data []byte) (err error) { - var token string - if err = json.Unmarshal(data, &token); err != nil { - return - } - *t, err = GetLongMytoken(token) - return -} - -// GetLongMytoken returns the long / jwt of a Mytoken; the passed token can be a jwt or a short token -func GetLongMytoken(token string) (Token, error) { - if utils.IsJWT(token) { - return Token(token), nil - } - shortToken := transfercoderepo.ParseShortToken(token) - token, valid, dbErr := shortToken.JWT(nil) - var validErr error - if !valid { - validErr = fmt.Errorf("token not valid") - } - return Token(token), utils.ORErrors(dbErr, validErr) -} diff --git a/shared/mytoken/universalmytoken/universalmytoken.go b/shared/mytoken/universalmytoken/universalmytoken.go new file mode 100644 index 00000000..9db539fe --- /dev/null +++ b/shared/mytoken/universalmytoken/universalmytoken.go @@ -0,0 +1,52 @@ +package universalmytoken + +import ( + "encoding/json" + "fmt" + + "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/transfercoderepo" + "github.com/oidc-mytoken/server/shared/model" + "github.com/oidc-mytoken/server/shared/utils" +) + +// UniversalMytoken is a type used for Mytokens passed in http requests; these can be normal Mytoken or a short token. This type will always provide a jwt of the (long) Mytoken +type UniversalMytoken struct { + JWT string + OriginalToken string + OriginalTokenType model.ResponseType +} + +// UnmarshalJSON implements the json.Unmarshaler interface +func (t *UniversalMytoken) UnmarshalJSON(data []byte) (err error) { + var token string + if err = json.Unmarshal(data, &token); err != nil { + return + } + *t, err = Parse(token) + return +} + +// Parse parses a mytoken string (that can be a long or short mytoken) into an UniversalMytoken holding the JWT +func Parse(token string) (UniversalMytoken, error) { + if token == "" { + return UniversalMytoken{}, fmt.Errorf("token not valid") + } + if utils.IsJWT(token) { + return UniversalMytoken{ + JWT: token, + OriginalToken: token, + OriginalTokenType: model.ResponseTypeToken, + }, nil + } + shortToken := transfercoderepo.ParseShortToken(token) + jwt, valid, dbErr := shortToken.JWT(nil) + var validErr error + if !valid { + validErr = fmt.Errorf("token not valid") + } + return UniversalMytoken{ + JWT: jwt, + OriginalToken: token, + OriginalTokenType: model.ResponseTypeShortToken, + }, utils.ORErrors(dbErr, validErr) +} From b4350070b1185458f2ee04ad51558334b9a8dc17 Mon Sep 17 00:00:00 2001 From: zachmann Date: Fri, 9 Jul 2021 16:33:40 +0200 Subject: [PATCH 23/61] move some hashing to SHA3; use a hash for the mytoken sub --- .../authcodeinforepo/state/consentcode.go | 2 +- .../db/dbrepo/authcodeinforepo/state/state.go | 4 ++-- internal/utils/hashUtils/hashUtils.go | 20 ++++++++++++++++--- internal/utils/utils.go | 12 +++++++++++ shared/mytoken/pkg/mytoken.go | 6 +++--- 5 files changed, 35 insertions(+), 9 deletions(-) create mode 100644 internal/utils/utils.go diff --git a/internal/db/dbrepo/authcodeinforepo/state/consentcode.go b/internal/db/dbrepo/authcodeinforepo/state/consentcode.go index 098443ee..58d63e75 100644 --- a/internal/db/dbrepo/authcodeinforepo/state/consentcode.go +++ b/internal/db/dbrepo/authcodeinforepo/state/consentcode.go @@ -43,7 +43,7 @@ func (c *ConsentCode) String() string { // GetState returns the state linked to a ConsentCode func (c *ConsentCode) GetState() string { if c.state == "" { - c.state = hashUtils.HMACSHA512Str([]byte("state"), []byte(c.r))[:stateLen] + c.encodedInfo + c.state = hashUtils.HMACSHA3Str([]byte("state"), []byte(c.r))[:stateLen] + c.encodedInfo } return c.state } diff --git a/internal/db/dbrepo/authcodeinforepo/state/state.go b/internal/db/dbrepo/authcodeinforepo/state/state.go index b442e12d..05dbd517 100644 --- a/internal/db/dbrepo/authcodeinforepo/state/state.go +++ b/internal/db/dbrepo/authcodeinforepo/state/state.go @@ -28,7 +28,7 @@ func NewState(state string) *State { // Hash returns the hash for this State func (s *State) Hash() string { if s.hash == "" { - s.hash = hashUtils.SHA512Str([]byte(s.state)) + s.hash = hashUtils.SHA3_512Str([]byte(s.state)) } return s.hash } @@ -36,7 +36,7 @@ func (s *State) Hash() string { // PollingCode returns the polling code for this State func (s *State) PollingCode() string { if s.pollingCode == "" { - s.pollingCode = hashUtils.HMACSHA512Str([]byte("polling_code"), []byte(s.state))[:config.Get().Features.Polling.Len] + s.pollingCode = hashUtils.HMACSHA3Str([]byte("polling_code"), []byte(s.state))[:config.Get().Features.Polling.Len] log.WithField("state", s.state).WithField("polling_code", s.pollingCode).Debug("Created polling_code for state") } return s.pollingCode diff --git a/internal/utils/hashUtils/hashUtils.go b/internal/utils/hashUtils/hashUtils.go index 44e51c62..62979a2d 100644 --- a/internal/utils/hashUtils/hashUtils.go +++ b/internal/utils/hashUtils/hashUtils.go @@ -4,6 +4,8 @@ import ( "crypto/hmac" "crypto/sha512" "encoding/base64" + + "golang.org/x/crypto/sha3" ) // SHA512 hashes the passed data with sha512 @@ -17,9 +19,21 @@ func SHA512Str(data []byte) string { return base64.StdEncoding.EncodeToString(hash[:]) } -// HMACSHA512Str creates a hmac using sha512 -func HMACSHA512Str(data, secret []byte) string { - h := hmac.New(sha512.New, secret) +// HMACSHA3Str creates a hmac using sha512 +func HMACSHA3Str(data, secret []byte) string { + h := hmac.New(sha3.New512, secret) hmac := h.Sum(data) return base64.StdEncoding.EncodeToString(hmac) } + +// SHA3_256Str hashes the passed data with SHA3 256 +func SHA3_256Str(data []byte) string { + hash := sha3.Sum256(data) + return base64.StdEncoding.EncodeToString(hash[:]) +} + +// SHA3_512Str hashes the passed data with SHA3 512 +func SHA3_512Str(data []byte) string { + hash := sha3.Sum512(data) + return base64.StdEncoding.EncodeToString(hash[:]) +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go new file mode 100644 index 00000000..22c971c7 --- /dev/null +++ b/internal/utils/utils.go @@ -0,0 +1,12 @@ +package utils + +import ( + "github.com/oidc-mytoken/server/internal/utils/hashUtils" + "github.com/oidc-mytoken/server/shared/utils/issuerUtils" +) + +// CreateMytokenSubject creates the subject of a Mytoken from the oidc subject and oidc issuer +func CreateMytokenSubject(oidcSub, oidcIss string) string { + comb := issuerUtils.CombineSubIss(oidcSub, oidcIss) + return hashUtils.SHA3_256Str([]byte(comb)) +} diff --git a/shared/mytoken/pkg/mytoken.go b/shared/mytoken/pkg/mytoken.go index 35024ade..57a125e3 100644 --- a/shared/mytoken/pkg/mytoken.go +++ b/shared/mytoken/pkg/mytoken.go @@ -5,6 +5,7 @@ import ( jwt "github.com/dgrijalva/jwt-go" "github.com/jmoiron/sqlx" + "github.com/oidc-mytoken/server/internal/utils" "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/config" @@ -17,7 +18,6 @@ import ( event "github.com/oidc-mytoken/server/shared/mytoken/event/pkg" "github.com/oidc-mytoken/server/shared/mytoken/pkg/mtid" "github.com/oidc-mytoken/server/shared/mytoken/restrictions" - "github.com/oidc-mytoken/server/shared/utils/issuerUtils" "github.com/oidc-mytoken/server/shared/utils/unixtime" ) @@ -63,7 +63,7 @@ func (mt *Mytoken) verifySubject() bool { if mt.Subject == "" { return false } - if mt.Subject != issuerUtils.CombineSubIss(mt.OIDCSubject, mt.OIDCIssuer) { + if mt.Subject != utils.CreateMytokenSubject(mt.OIDCSubject, mt.OIDCIssuer) { return false } return true @@ -93,7 +93,7 @@ func NewMytoken(oidcSub, oidcIss string, r restrictions.Restrictions, c, sc api. IssuedAt: now, NotBefore: now, Issuer: config.Get().IssuerURL, - Subject: issuerUtils.CombineSubIss(oidcSub, oidcIss), + Subject: utils.CreateMytokenSubject(oidcSub, oidcIss), Audience: config.Get().IssuerURL, OIDCIssuer: oidcIss, OIDCSubject: oidcSub, From e4d5c80f3d8785a53bf0859cfa9c493e97a326bc Mon Sep 17 00:00:00 2001 From: zachmann Date: Mon, 12 Jul 2021 16:03:20 +0200 Subject: [PATCH 24/61] delete encryption key on mytoken revocation --- internal/db/dbmigrate/v0.3.0.go | 1 + .../encryptionkeyrepo/encryptionkeyRepo.go | 85 +++++++++++++++++++ .../mytokenrepo/mytokenrepohelper/helpers.go | 26 ++++-- .../dbrepo/refreshtokenrepo/refreshtoken.go | 70 ++------------- shared/mytoken/mytokenHandler.go | 3 +- shared/mytoken/rotation/rotation.go | 4 +- 6 files changed, 116 insertions(+), 73 deletions(-) create mode 100644 internal/db/dbrepo/encryptionkeyrepo/encryptionkeyRepo.go diff --git a/internal/db/dbmigrate/v0.3.0.go b/internal/db/dbmigrate/v0.3.0.go index c35ebf75..4203d4a5 100644 --- a/internal/db/dbmigrate/v0.3.0.go +++ b/internal/db/dbmigrate/v0.3.0.go @@ -4,6 +4,7 @@ var v0_3_0_Before = []string{ // Tables "ALTER TABLE AuthInfo ADD rotation json NULL", "DROP TRIGGER updtrigger", + "ALTER TABLE RT_EncryptionKeys ADD CONSTRAINT RT_EncryptionKeys_FK_2 FOREIGN KEY (MT_id) REFERENCES MTokens(id) ON DELETE CASCADE ON UPDATE CASCADE", // Predefined Values "INSERT IGNORE INTO Events (event) VALUES('token_rotated')", diff --git a/internal/db/dbrepo/encryptionkeyrepo/encryptionkeyRepo.go b/internal/db/dbrepo/encryptionkeyrepo/encryptionkeyRepo.go new file mode 100644 index 00000000..4d8985c2 --- /dev/null +++ b/internal/db/dbrepo/encryptionkeyrepo/encryptionkeyRepo.go @@ -0,0 +1,85 @@ +package encryptionkeyrepo + +import ( + "encoding/base64" + + "github.com/jmoiron/sqlx" + "github.com/oidc-mytoken/server/internal/db" + "github.com/oidc-mytoken/server/shared/mytoken/pkg/mtid" + "github.com/oidc-mytoken/server/shared/utils/cryptUtils" +) + +// ReencryptEncryptionKey re-encrypts the encryption key for a mytoken. This is needed when the mytoken changes, e.g. on token rotation +func ReencryptEncryptionKey(tx *sqlx.Tx, tokenID mtid.MTID, oldJWT, newJWT string) error { + return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { + keyID, err := getEncryptionKeyID(tx, tokenID) + if err != nil { + return err + } + var encryptedKey string + if err = tx.Get(&encryptedKey, `SELECT encryption_key FROM EncryptionKeys WHERE id=?`, keyID); err != nil { + return err + } + key, err := cryptUtils.AES256Decrypt(encryptedKey, oldJWT) + if err != nil { + return err + } + updatedKey, err := cryptUtils.AES256Encrypt(key, newJWT) + if err != nil { + return err + } + _, err = tx.Exec(`UPDATE EncryptionKeys SET encryption_key=? WHERE id=?`, updatedKey, keyID) + return err + }) +} + +// DeleteEncryptionKey deletes the encryption key for a mytoken. +func DeleteEncryptionKey(tx *sqlx.Tx, tokenID mtid.MTID) error { + return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { + keyID, err := getEncryptionKeyID(tx, tokenID) + if err != nil { + return err + } + if _, err = tx.Exec(`DELETE FROM EncryptionKeys WHERE id=?`, keyID); err != nil { + return err + } + return nil + }) +} + +// GetEncryptionKey returns the encryption key and the rtid for a mytoken +func GetEncryptionKey(tx *sqlx.Tx, tokenID mtid.MTID, jwt string) (key []byte, rtID uint64, err error) { + err = db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { + var res struct { + EncryptedKey EncryptionKey `db:"encryption_key"` + ID uint64 `db:"rt_id"` + } + if err = tx.Get(&res, `SELECT encryption_key, rt_id FROM MyTokens WHERE id=?`, tokenID); err != nil { + return err + } + rtID = res.ID + key, err = res.EncryptedKey.Decrypt(jwt) + return err + }) + return +} + +// EncryptionKey is a type for the encryption key stored in the db +type EncryptionKey string + +// Decrypt returns the decrypted encryption key +func (k EncryptionKey) Decrypt(jwt string) ([]byte, error) { + decryptedKey, err := cryptUtils.AES256Decrypt(string(k), jwt) + if err != nil { + return nil, err + } + return base64.StdEncoding.DecodeString(decryptedKey) +} + +// getEncryptionKeyID returns the id of the encryption key used for encrypting the RT linked to this mytoken +func getEncryptionKeyID(tx *sqlx.Tx, myID mtid.MTID) (keyID uint64, err error) { + err = db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { + return tx.Get(&keyID, `SELECT key_id FROM RT_EncryptionKeys WHERE MT_id=? AND rt_id=(SELECT rt_id FROM MTokens WHERE id=?)`, myID, myID) + }) + return +} diff --git a/internal/db/dbrepo/mytokenrepo/mytokenrepohelper/helpers.go b/internal/db/dbrepo/mytokenrepo/mytokenrepohelper/helpers.go index 028df5b5..1742237d 100644 --- a/internal/db/dbrepo/mytokenrepo/mytokenrepohelper/helpers.go +++ b/internal/db/dbrepo/mytokenrepo/mytokenrepohelper/helpers.go @@ -6,8 +6,8 @@ import ( "github.com/jmoiron/sqlx" "github.com/oidc-mytoken/api/v0" - "github.com/oidc-mytoken/server/internal/db" + "github.com/oidc-mytoken/server/internal/db/dbrepo/encryptionkeyrepo" "github.com/oidc-mytoken/server/internal/utils/hashUtils" "github.com/oidc-mytoken/server/shared/mytoken/pkg/mtid" ) @@ -45,8 +45,8 @@ func GetMTRootID(id mtid.MTID) (mtid.MTID, bool, error) { // recursiveRevokeMT revokes the passed mytoken as well as all children func recursiveRevokeMT(tx *sqlx.Tx, id mtid.MTID) error { return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { - _, err := tx.Exec(` - DELETE FROM MTokens WHERE id=ANY( + var effectedMTIDs []mtid.MTID + if err := tx.Select(&effectedMTIDs, ` WITH Recursive childs AS ( @@ -55,8 +55,21 @@ func recursiveRevokeMT(tx *sqlx.Tx, id mtid.MTID) error { SELECT mt.id, mt.parent_id FROM MTokens mt INNER JOIN childs c WHERE mt.parent_id=c.id ) SELECT id - FROM childs - )`, id) + FROM childs`, id); err != nil { + return err + } + query, args, err := sqlx.In(`DELETE FROM EncryptionKeys WHERE id=ANY(SELECT key_id FROM RT_EncryptionKeys WHERE MT_id IN (?))`, effectedMTIDs) + if err != nil { + return err + } + if _, err = tx.Exec(query, args...); err != nil { + return err + } + query, args, err = sqlx.In(`DELETE FROM MTokens WHERE id IN (?)`, effectedMTIDs) + if err != nil { + return err + } + _, err = tx.Exec(query, args...) return err }) } @@ -106,6 +119,9 @@ func UpdateSeqNo(tx *sqlx.Tx, id mtid.MTID, seqno uint64) error { // revokeMT revokes the passed mytoken but no children func revokeMT(tx *sqlx.Tx, id mtid.MTID) error { return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { + if err := encryptionkeyrepo.DeleteEncryptionKey(tx, id); err != nil { + return err + } _, err := tx.Exec(`DELETE FROM MTokens WHERE id=?`, id) return err }) diff --git a/internal/db/dbrepo/refreshtokenrepo/refreshtoken.go b/internal/db/dbrepo/refreshtokenrepo/refreshtoken.go index 0cc87b5e..ed6fc611 100644 --- a/internal/db/dbrepo/refreshtokenrepo/refreshtoken.go +++ b/internal/db/dbrepo/refreshtokenrepo/refreshtoken.go @@ -1,10 +1,9 @@ package refreshtokenrepo import ( - "encoding/base64" - "github.com/jmoiron/sqlx" "github.com/oidc-mytoken/server/internal/db" + "github.com/oidc-mytoken/server/internal/db/dbrepo/encryptionkeyrepo" helper "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/mytokenrepohelper" "github.com/oidc-mytoken/server/shared/mytoken/pkg/mtid" "github.com/oidc-mytoken/server/shared/utils/cryptUtils" @@ -13,7 +12,7 @@ import ( // UpdateRefreshToken updates a refresh token in the database, all occurrences of the RT are updated. func UpdateRefreshToken(tx *sqlx.Tx, tokenID mtid.MTID, newRT, jwt string) error { return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { - key, rtID, err := GetEncryptionKey(tx, tokenID, jwt) + key, rtID, err := encryptionkeyrepo.GetEncryptionKey(tx, tokenID, jwt) if err != nil { return err } @@ -26,64 +25,13 @@ func UpdateRefreshToken(tx *sqlx.Tx, tokenID mtid.MTID, newRT, jwt string) error }) } -// ReencryptEncryptionKey re-encrypts the encryption key for a mytoken. This is needed when the mytoken changes, e.g. on token rotation -func ReencryptEncryptionKey(tx *sqlx.Tx, tokenID mtid.MTID, oldJWT, newJWT string) error { - return db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { - keyID, err := getEncryptionKeyID(tx, tokenID) - if err != nil { - return err - } - var encryptedKey string - if err = tx.Get(&encryptedKey, `SELECT encryption_key FROM EncryptionKeys WHERE id=?`, keyID); err != nil { - return err - } - key, err := cryptUtils.AES256Decrypt(encryptedKey, oldJWT) - if err != nil { - return err - } - updatedKey, err := cryptUtils.AES256Encrypt(key, newJWT) - if err != nil { - return err - } - _, err = tx.Exec(`UPDATE EncryptionKeys SET encryption_key=? WHERE id=?`, updatedKey, keyID) - return err - }) -} - -// GetEncryptionKey returns the encryption key and the rtid for a mytoken -func GetEncryptionKey(tx *sqlx.Tx, tokenID mtid.MTID, jwt string) (key []byte, rtID uint64, err error) { - err = db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { - var res struct { - EncryptedKey encryptionKey `db:"encryption_key"` - ID uint64 `db:"rt_id"` - } - if err = tx.Get(&res, `SELECT encryption_key, rt_id FROM MyTokens WHERE id=?`, tokenID); err != nil { - return err - } - rtID = res.ID - key, err = res.EncryptedKey.decrypt(jwt) - return err - }) - return -} - -type encryptionKey string - -func (k encryptionKey) decrypt(jwt string) ([]byte, error) { - decryptedKey, err := cryptUtils.AES256Decrypt(string(k), jwt) - if err != nil { - return nil, err - } - return base64.StdEncoding.DecodeString(decryptedKey) -} - type rtStruct struct { - RT string `db:"refresh_token"` - Key encryptionKey `db:"encryption_key"` + RT string `db:"refresh_token"` + Key encryptionkeyrepo.EncryptionKey `db:"encryption_key"` } func (rt rtStruct) decrypt(jwt string) (string, error) { - key, err := rt.Key.decrypt(jwt) + key, err := rt.Key.Decrypt(jwt) if err != nil { return "", err } @@ -126,11 +74,3 @@ func GetRTID(tx *sqlx.Tx, myID mtid.MTID) (rtID uint64, err error) { }) return } - -// getEncryptionKeyID returns the id of the encryption key used for encrypting the RT linked to this mytoken -func getEncryptionKeyID(tx *sqlx.Tx, myID mtid.MTID) (keyID uint64, err error) { - err = db.RunWithinTransaction(tx, func(tx *sqlx.Tx) error { - return tx.Get(&keyID, `SELECT key_id FROM RT_EncryptionKeys WHERE MT_id=? AND rt_id=(SELECT rt_id FROM MTokens WHERE id=?)`, myID, myID) - }) - return -} diff --git a/shared/mytoken/mytokenHandler.go b/shared/mytoken/mytokenHandler.go index 358e9f5e..417866ef 100644 --- a/shared/mytoken/mytokenHandler.go +++ b/shared/mytoken/mytokenHandler.go @@ -9,6 +9,7 @@ import ( "github.com/gofiber/fiber/v2" "github.com/jmoiron/sqlx" + "github.com/oidc-mytoken/server/internal/db/dbrepo/encryptionkeyrepo" "github.com/oidc-mytoken/server/internal/utils/cookies" "github.com/oidc-mytoken/server/shared/mytoken/rotation" log "github.com/sirupsen/logrus" @@ -272,7 +273,7 @@ func createMytokenEntry(parent *mytoken.Mytoken, req *response.MytokenFromMytoke ste := mytokenrepo.NewMytokenEntry( mytoken.NewMytoken(parent.OIDCSubject, parent.OIDCIssuer, r, c, sc, req.Rotation), req.Name, networkData) - encryptionKey, _, err := refreshtokenrepo.GetEncryptionKey(nil, parent.ID, req.Mytoken.JWT) + encryptionKey, _, err := encryptionkeyrepo.GetEncryptionKey(nil, parent.ID, req.Mytoken.JWT) if err != nil { log.WithError(err).Error() return ste, model.ErrorToInternalServerErrorResponse(err) diff --git a/shared/mytoken/rotation/rotation.go b/shared/mytoken/rotation/rotation.go index 4e19fb08..bca715a2 100644 --- a/shared/mytoken/rotation/rotation.go +++ b/shared/mytoken/rotation/rotation.go @@ -4,8 +4,8 @@ import ( "github.com/jmoiron/sqlx" "github.com/oidc-mytoken/api/v0" "github.com/oidc-mytoken/server/internal/db" + "github.com/oidc-mytoken/server/internal/db/dbrepo/encryptionkeyrepo" helper "github.com/oidc-mytoken/server/internal/db/dbrepo/mytokenrepo/mytokenrepohelper" - "github.com/oidc-mytoken/server/internal/db/dbrepo/refreshtokenrepo" "github.com/oidc-mytoken/server/internal/endpoints/token/mytoken/pkg" "github.com/oidc-mytoken/server/shared/model" eventService "github.com/oidc-mytoken/server/shared/mytoken/event" @@ -23,7 +23,7 @@ func rotateMytoken(tx *sqlx.Tx, oldJWT string, old *mytoken.Mytoken, clientMetaD if err = helper.UpdateSeqNo(tx, rotated.ID, rotated.SeqNo); err != nil { return err } - if err = refreshtokenrepo.ReencryptEncryptionKey(tx, rotated.ID, oldJWT, jwt); err != nil { + if err = encryptionkeyrepo.ReencryptEncryptionKey(tx, rotated.ID, oldJWT, jwt); err != nil { return err } return eventService.LogEvent(tx, eventService.MTEvent{ From b6d9676a9e298c6a0ccebaef37652fce10478984 Mon Sep 17 00:00:00 2001 From: zachmann Date: Tue, 20 Jul 2021 10:45:12 +0200 Subject: [PATCH 25/61] add possibility to create MT from web interface --- .../authcodeinforepo/state/consentcode.go | 2 +- .../db/dbrepo/authcodeinforepo/state/state.go | 2 +- .../transfercoderepo/proxytoken.go | 3 +- internal/endpoints/consent/consent.go | 1 + internal/endpoints/consent/pkg/restriction.go | 2 - internal/server/constants.go | 1 + internal/server/handlers.go | 10 +- internal/server/server.go | 1 - internal/server/web/layouts/main.mustache | 2 +- internal/server/web/partials/mytoken.mustache | 170 ++++ internal/server/web/partials/navbar.mustache | 4 +- .../partials/restrictions-gui-editor.mustache | 114 +++ internal/server/web/partials/scripts.mustache | 17 +- internal/server/web/partials/style.mustache | 13 +- .../{mytokens.mustache => tokeninfo.mustache} | 0 internal/server/web/sites/consent.mustache | 4 +- internal/server/web/sites/home.mustache | 12 +- internal/server/web/static/css/colors.css | 29 + .../static/css/lib/bootstrap4-toggle.min.css | 10 + .../web/static/css/mytoken-loggedin.css | 15 - internal/server/web/static/css/restr-gui.css | 3 + internal/server/web/static/js/capabilities.js | 7 + internal/server/web/static/js/consent.js | 142 ---- internal/server/web/static/js/countries.js | 742 ++++++++++++++++++ internal/server/web/static/js/create-mt.js | 201 +++++ internal/server/web/static/js/discovery.js | 3 +- .../static/js/lib/bootstrap4-toggle.min.js | 11 + .../js/lib/bootstrap4-toggle.min.js.map | 1 + internal/server/web/static/js/login.js | 1 + internal/server/web/static/js/restr-gui.js | 314 ++++++++ internal/server/web/static/js/restrictions.js | 156 ++++ internal/server/web/static/js/storage.js | 4 +- internal/server/web/static/js/tokens.js | 21 + internal/server/web/static/js/utils.js | 10 +- internal/utils/hashUtils/hashUtils.go | 7 +- 35 files changed, 1853 insertions(+), 182 deletions(-) create mode 100644 internal/server/web/partials/mytoken.mustache create mode 100644 internal/server/web/partials/restrictions-gui-editor.mustache rename internal/server/web/partials/{mytokens.mustache => tokeninfo.mustache} (100%) create mode 100644 internal/server/web/static/css/colors.css create mode 100644 internal/server/web/static/css/lib/bootstrap4-toggle.min.css create mode 100644 internal/server/web/static/css/restr-gui.css create mode 100644 internal/server/web/static/js/capabilities.js create mode 100644 internal/server/web/static/js/countries.js create mode 100644 internal/server/web/static/js/create-mt.js create mode 100644 internal/server/web/static/js/lib/bootstrap4-toggle.min.js create mode 100644 internal/server/web/static/js/lib/bootstrap4-toggle.min.js.map create mode 100644 internal/server/web/static/js/restr-gui.js create mode 100644 internal/server/web/static/js/restrictions.js diff --git a/internal/db/dbrepo/authcodeinforepo/state/consentcode.go b/internal/db/dbrepo/authcodeinforepo/state/consentcode.go index 58d63e75..eef878dc 100644 --- a/internal/db/dbrepo/authcodeinforepo/state/consentcode.go +++ b/internal/db/dbrepo/authcodeinforepo/state/consentcode.go @@ -43,7 +43,7 @@ func (c *ConsentCode) String() string { // GetState returns the state linked to a ConsentCode func (c *ConsentCode) GetState() string { if c.state == "" { - c.state = hashUtils.HMACSHA3Str([]byte("state"), []byte(c.r))[:stateLen] + c.encodedInfo + c.state = hashUtils.HMACBasedHash([]byte(c.r))[:stateLen] + c.encodedInfo } return c.state } diff --git a/internal/db/dbrepo/authcodeinforepo/state/state.go b/internal/db/dbrepo/authcodeinforepo/state/state.go index 05dbd517..6b2c2e25 100644 --- a/internal/db/dbrepo/authcodeinforepo/state/state.go +++ b/internal/db/dbrepo/authcodeinforepo/state/state.go @@ -36,7 +36,7 @@ func (s *State) Hash() string { // PollingCode returns the polling code for this State func (s *State) PollingCode() string { if s.pollingCode == "" { - s.pollingCode = hashUtils.HMACSHA3Str([]byte("polling_code"), []byte(s.state))[:config.Get().Features.Polling.Len] + s.pollingCode = hashUtils.HMACBasedHash([]byte(s.state))[:config.Get().Features.Polling.Len] log.WithField("state", s.state).WithField("polling_code", s.pollingCode).Debug("Created polling_code for state") } return s.pollingCode diff --git a/internal/db/dbrepo/mytokenrepo/transfercoderepo/proxytoken.go b/internal/db/dbrepo/mytokenrepo/transfercoderepo/proxytoken.go index e64cf19e..44dd32c1 100644 --- a/internal/db/dbrepo/mytokenrepo/transfercoderepo/proxytoken.go +++ b/internal/db/dbrepo/mytokenrepo/transfercoderepo/proxytoken.go @@ -5,7 +5,6 @@ import ( "errors" "github.com/jmoiron/sqlx" - "github.com/oidc-mytoken/server/internal/db" "github.com/oidc-mytoken/server/internal/utils/hashUtils" "github.com/oidc-mytoken/server/shared/mytoken/pkg/mtid" @@ -87,7 +86,7 @@ func (pt *proxyToken) JWT(tx *sqlx.Tx) (jwt string, valid bool, err error) { JWT string `db:"jwt"` MTID mtid.MTID `db:"MT_id"` } - if err := tx.Get(&res, `SELECT jwt, MT_id FROM ProxyTokens WHERE id=?`, pt.id); err != nil { + if err = tx.Get(&res, `SELECT jwt, MT_id FROM ProxyTokens WHERE id=?`, pt.id); err != nil { return err } pt.encryptedJWT = res.JWT diff --git a/internal/endpoints/consent/consent.go b/internal/endpoints/consent/consent.go index 9837036e..545a7c37 100644 --- a/internal/endpoints/consent/consent.go +++ b/internal/endpoints/consent/consent.go @@ -28,6 +28,7 @@ func handleConsent(ctx *fiber.Ctx, authInfo *authcodeinforepo.AuthFlowInfoOut) e binding := map[string]interface{}{ "consent": true, "empty-navbar": true, + "restr-gui": true, "restrictions": pkg.WebRestrictions{Restrictions: authInfo.Restrictions}, "capabilities": pkg.WebCapabilities(c), "iss": authInfo.Issuer, diff --git a/internal/endpoints/consent/pkg/restriction.go b/internal/endpoints/consent/pkg/restriction.go index e07991c5..d44e25d3 100644 --- a/internal/endpoints/consent/pkg/restriction.go +++ b/internal/endpoints/consent/pkg/restriction.go @@ -2,7 +2,6 @@ package pkg import ( "encoding/json" - "fmt" "github.com/oidc-mytoken/server/shared/mytoken/restrictions" "github.com/oidc-mytoken/server/shared/utils" @@ -22,7 +21,6 @@ type WebRestrictions struct { // Text returns a textual (json) representation of this WebRestrictions func (r WebRestrictions) Text() string { data, _ := json.Marshal(r.Restrictions) - fmt.Println(string(data)) return string(data) } diff --git a/internal/server/constants.go b/internal/server/constants.go index 53ed731b..63eac998 100644 --- a/internal/server/constants.go +++ b/internal/server/constants.go @@ -3,3 +3,4 @@ package server const layoutMain = "layouts/main" const emptyNavbar = "empty-navbar" const loggedIn = "logged-in" +const restrictionsGUI = "restr-gui" diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 9637bb58..8e639d33 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -2,6 +2,10 @@ package server import ( "github.com/gofiber/fiber/v2" + "github.com/oidc-mytoken/api/v0" + consent "github.com/oidc-mytoken/server/internal/endpoints/consent/pkg" + "github.com/oidc-mytoken/server/shared/mytoken/restrictions" + "github.com/oidc-mytoken/server/shared/utils/unixtime" "github.com/oidc-mytoken/server/internal/config" ) @@ -23,7 +27,11 @@ func handleIndex(ctx *fiber.Ctx) error { func handleHome(ctx *fiber.Ctx) error { binding := map[string]interface{}{ - loggedIn: true, + loggedIn: true, + restrictionsGUI: true, + "restrictions": consent.WebRestrictions{Restrictions: restrictions.Restrictions{{ExpiresAt: unixtime.InSeconds(3600 * 24 * 7)}}}, + "capabilities": consent.WebCapabilities(api.AllCapabilities), + "subtoken-capabilities": consent.WebCapabilities(api.AllCapabilities), } return ctx.Render("sites/home", binding, layoutMain) } diff --git a/internal/server/server.go b/internal/server/server.go index 926f25d0..31fd7cbe 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -56,7 +56,6 @@ func init() { func initTemplateEngine() { engine := mustache.NewFileSystemPartials(http.FS(webFiles), ".mustache", http.FS(partials)) - // engine.Reload(version.DEV) serverConfig.Views = engine } diff --git a/internal/server/web/layouts/main.mustache b/internal/server/web/layouts/main.mustache index 3d0861ee..7be002a5 100644 --- a/internal/server/web/layouts/main.mustache +++ b/internal/server/web/layouts/main.mustache @@ -4,7 +4,7 @@ mytoken - + {{> style }} diff --git a/internal/server/web/partials/mytoken.mustache b/internal/server/web/partials/mytoken.mustache new file mode 100644 index 00000000..f31186a4 --- /dev/null +++ b/internal/server/web/partials/mytoken.mustache @@ -0,0 +1,170 @@ + +
+
+
+
+
+
Token Name
+ + Give the Mytoken a name, so you can identify it better. +
+ +
+
Token Type
+
+ + +
+ The Mytoken can be returned as a JWT or as a Short Mytoken. +
+
+ +
+
+
+
Token Rotation
+
+
+ + +
+ If the mytoken is used to request an Access Token, a new mytoken will be returned. +
+ +
+
+ + +
+ If the mytoken is used for other requests than requesting an Access Token, a new mytoken will be returned. +
+ +
+
+
+ + + The lifetime of a single Mytoken given in seconds. (Infinite lifetime if set to 0) +
+ +
+
+ + +
+ If the mytoken server detects a misuse, the mytoken will be automatically revoked. +
+
+
+
+ +
+ +
+
+
+
+

Choose Capabilities

+ + + {{#capabilities}} + + + + + {{/capabilities}} + +
+
+ + +
+
+
+
{{Name}}
+ +
+
{{Description}}
+ + {{#IsCreateMT}} +
+
+
+

The mytoken you are going to create, can create mytokens with the following capabilities:

+ + + {{#subtoken-capabilities}} + + + + + {{/subtoken-capabilities}} + +
+
+ + +
+
+
+
{{Name}}
+ +
+
{{Description}}
+
+
+
+ {{/IsCreateMT}} +
+
+
+ +
+
+
+

Set Restrictions

+ +
+ +
+ + + + + +
+ + {{>restrictions-gui-editor}} + +
+ +
+
+ +
+
+ + Create new Mytoken +
+ +
+
+

+ To obtain your Mytoken follow the following link and authenticate: + +

+
+ + Configure another Mytoken +
diff --git a/internal/server/web/partials/navbar.mustache b/internal/server/web/partials/navbar.mustache index 3e67cf47..64a9dd22 100644 --- a/internal/server/web/partials/navbar.mustache +++ b/internal/server/web/partials/navbar.mustache @@ -5,13 +5,13 @@ {{#logged-in}} @@ -27,8 +30,11 @@

             Get new Access Token
         
-        
- {{> mytokens}} +
+ {{> tokeninfo}} +
+
+ {{> mytoken}}
\ No newline at end of file diff --git a/internal/server/web/static/css/colors.css b/internal/server/web/static/css/colors.css new file mode 100644 index 00000000..6cd7b6b7 --- /dev/null +++ b/internal/server/web/static/css/colors.css @@ -0,0 +1,29 @@ +.bg-my_green { + background: #6DB966 +} +.bg-my_green_dark { + background: #007038 +} +.text-my_green{ + color: #007038 +} + +.bg-my_orange{ + background: #DF691A +} +.bg-my_orange_dark{ + background: #C73500 +} +.text-my_orange{ + color: #C73500 +} + +.bg-my_blue { + background: #1BA1E2 +} +.bg-my_blue_dark { + background: #006EAF +} +.text-my_blue{ + color: #006EAF +} diff --git a/internal/server/web/static/css/lib/bootstrap4-toggle.min.css b/internal/server/web/static/css/lib/bootstrap4-toggle.min.css new file mode 100644 index 00000000..42b1ec72 --- /dev/null +++ b/internal/server/web/static/css/lib/bootstrap4-toggle.min.css @@ -0,0 +1,10 @@ +/*\ +|*| ======================================================================== +|*| Bootstrap Toggle: bootstrap4-toggle.css v3.6.1 +|*| https://gitbrent.github.io/bootstrap4-toggle/ +|*| ======================================================================== +|*| Copyright 2018-2019 Brent Ely +|*| Licensed under MIT +|*| ======================================================================== +\*/ +.btn-group-xs>.btn,.btn-xs{padding:.35rem .4rem .25rem .4rem;font-size:.875rem;line-height:.5;border-radius:.2rem}.checkbox label .toggle,.checkbox-inline .toggle{margin-left:-1.25rem;margin-right:.35rem}.toggle{position:relative;overflow:hidden}.toggle.btn.btn-light,.toggle.btn.btn-outline-light{border-color:rgba(0,0,0,.15)}.toggle input[type=checkbox]{display:none}.toggle-group{position:absolute;width:200%;top:0;bottom:0;left:0;transition:left .35s;-webkit-transition:left .35s;-moz-user-select:none;-webkit-user-select:none}.toggle-group label,.toggle-group span{cursor:pointer}.toggle.off .toggle-group{left:-100%}.toggle-on{position:absolute;top:0;bottom:0;left:0;right:50%;margin:0;border:0;border-radius:0}.toggle-off{position:absolute;top:0;bottom:0;left:50%;right:0;margin:0;border:0;border-radius:0;box-shadow:none}.toggle-handle{position:relative;margin:0 auto;padding-top:0;padding-bottom:0;height:100%;width:0;border-width:0 1px;background-color:#fff}.toggle.btn-outline-primary .toggle-handle{background-color:var(--primary);border-color:var(--primary)}.toggle.btn-outline-secondary .toggle-handle{background-color:var(--secondary);border-color:var(--secondary)}.toggle.btn-outline-success .toggle-handle{background-color:var(--success);border-color:var(--success)}.toggle.btn-outline-danger .toggle-handle{background-color:var(--danger);border-color:var(--danger)}.toggle.btn-outline-warning .toggle-handle{background-color:var(--warning);border-color:var(--warning)}.toggle.btn-outline-info .toggle-handle{background-color:var(--info);border-color:var(--info)}.toggle.btn-outline-light .toggle-handle{background-color:var(--light);border-color:var(--light)}.toggle.btn-outline-dark .toggle-handle{background-color:var(--dark);border-color:var(--dark)}.toggle[class*=btn-outline]:hover .toggle-handle{background-color:var(--light);opacity:.5}.toggle.btn{min-width:3.7rem;min-height:2.15rem}.toggle-on.btn{padding-right:1.5rem}.toggle-off.btn{padding-left:1.5rem}.toggle.btn-lg{min-width:5rem;min-height:2.815rem}.toggle-on.btn-lg{padding-right:2rem}.toggle-off.btn-lg{padding-left:2rem}.toggle-handle.btn-lg{width:2.5rem}.toggle.btn-sm{min-width:3.125rem;min-height:1.938rem}.toggle-on.btn-sm{padding-right:1rem}.toggle-off.btn-sm{padding-left:1rem}.toggle.btn-xs{min-width:2.19rem;min-height:1.375rem}.toggle-on.btn-xs{padding-right:.8rem}.toggle-off.btn-xs{padding-left:.8rem} diff --git a/internal/server/web/static/css/mytoken-loggedin.css b/internal/server/web/static/css/mytoken-loggedin.css index 0f923659..bc3c7792 100644 --- a/internal/server/web/static/css/mytoken-loggedin.css +++ b/internal/server/web/static/css/mytoken-loggedin.css @@ -1,18 +1,3 @@ - -#logout { - background: #fa3e34; -} -/*#other {*/ -/* background: #52a66f;*/ -/*}*/ -/*#settings {*/ -/* background: #fac43c;*/ -/*}*/ -#settings { - background: #30768b; -} - - .hover-text { display: none; } diff --git a/internal/server/web/static/css/restr-gui.css b/internal/server/web/static/css/restr-gui.css new file mode 100644 index 00000000..a5babbf2 --- /dev/null +++ b/internal/server/web/static/css/restr-gui.css @@ -0,0 +1,3 @@ +.table-item{ + padding: 0.375rem 0.75rem; +} \ No newline at end of file diff --git a/internal/server/web/static/js/capabilities.js b/internal/server/web/static/js/capabilities.js new file mode 100644 index 00000000..0cd9376e --- /dev/null +++ b/internal/server/web/static/js/capabilities.js @@ -0,0 +1,7 @@ + +$('#cp-create_mytoken').click(function() { + let enabled = $(this).prop("checked"); + let capabilityCheck = $('.subtoken-capability-check'); + capabilityCheck.prop("checked", enabled); + capabilityCheck.prop("disabled", !enabled); +}); diff --git a/internal/server/web/static/js/consent.js b/internal/server/web/static/js/consent.js index 6612ce4b..0c768818 100644 --- a/internal/server/web/static/js/consent.js +++ b/internal/server/web/static/js/consent.js @@ -1,140 +1,4 @@ -function parseRestriction() { - let howManyClausesRestrictIP = 0; - let howManyClausesRestrictScope = 0; - let howManyClausesRestrictAud = 0; - let howManyClausesRestrictUsages = 0; - let expires = 0; - let doesNotExpire = false; - restrictions.forEach(function (r) { - if (r['scope'] !== undefined) { - howManyClausesRestrictScope++; - } - let aud = r['audience']; - if (aud !== undefined && aud.length > 0) { - howManyClausesRestrictAud++; - } - let ip = r['ip']; - let ipW = r['geoip_white']; - let ipB = r['geoip_black']; - if ((ip !== undefined && ip.length > 0) || - (ipW !== undefined && ipW.length > 0) || - (ipB !== undefined && ipB.length > 0)) { - howManyClausesRestrictIP++; - } - if (r['usages_other']!==undefined || r['usages_AT']!==undefined) { - howManyClausesRestrictUsages++; - } - let exp = r['exp']; - if (exp===undefined || exp===0) { - doesNotExpire = true - } else if (exp>expires) { - expires=exp; - } - }) - if (doesNotExpire) { - expires = 0; - } - let iconTime = $('#r-icon-time'); - let iconIP = $('#r-icon-ip'); - let iconScope = $('#r-icon-scope'); - let iconAud = $('#r-icon-aud'); - let iconUsages = $('#r-icon-usages'); - if (howManyClausesRestrictIP===restrictions.length && restrictions.length > 0) { - iconIP.addClass( 'text-success'); - iconIP.removeClass( 'text-warning'); - iconIP.removeClass( 'text-danger'); - iconIP.attr('data-original-title', "The IPs from which this token can be used are restricted."); - } else { - iconIP.addClass( 'text-warning'); - iconIP.removeClass( 'text-success'); - iconIP.removeClass( 'text-danger'); - iconIP.attr('data-original-title', "This token can be used from any IP."); - } - if (howManyClausesRestrictScope===restrictions.length && restrictions.length > 0) { - iconScope.addClass( 'text-success'); - iconScope.removeClass( 'text-warning'); - iconScope.removeClass( 'text-danger'); - iconScope.attr('data-original-title', "This token has restrictions for scopes."); - } else { - iconScope.addClass( 'text-warning'); - iconScope.removeClass( 'text-success'); - iconScope.removeClass( 'text-danger'); - iconScope.attr('data-original-title', "This token can use all configured scopes."); - } - if (howManyClausesRestrictAud===restrictions.length && restrictions.length > 0) { - iconAud.addClass( 'text-success'); - iconAud.removeClass( 'text-warning'); - iconAud.removeClass( 'text-danger'); - iconAud.attr('data-original-title', "This token can only obtain access tokens with restricted audiences."); - } else { - iconAud.addClass( 'text-warning'); - iconAud.removeClass( 'text-success'); - iconAud.removeClass( 'text-danger'); - iconAud.attr('data-original-title', "This token can obtain access tokens with any audiences."); - } - if (howManyClausesRestrictUsages===restrictions.length && restrictions.length > 0) { - iconUsages.addClass( 'text-success'); - iconUsages.removeClass( 'text-warning'); - iconUsages.removeClass( 'text-danger'); - iconUsages.attr('data-original-title', "This token can only be used a limited number of times."); - } else { - iconUsages.addClass( 'text-warning'); - iconUsages.removeClass( 'text-success'); - iconUsages.removeClass( 'text-danger'); - iconUsages.attr('data-original-title', "This token can be used an infinite number of times."); - } - if (expires===0) { - iconTime.addClass( 'text-danger'); - iconTime.removeClass( 'text-success'); - iconTime.removeClass( 'text-warning'); - iconTime.attr('data-original-title', "This token does not expire!"); - } else if ((expires - Date.now()/1000)> 3*24*3600) { - iconTime.addClass( 'text-warning'); - iconTime.removeClass( 'text-success'); - iconTime.removeClass( 'text-danger'); - iconTime.attr('data-original-title', "This token is long-lived."); - } else { - iconTime.addClass( 'text-success'); - iconTime.removeClass( 'text-warning'); - iconTime.removeClass( 'text-danger'); - iconTime.attr('data-original-title', "This token expires within 3 days."); - } -} - -function newJSONEditor(textareaID) { - return new Behave({ - textarea: document.getElementById(textareaID), - replaceTab: true, - softTabs: true, - tabSize: 4, - autoOpen: true, - overwrite: true, - autoStrip: true, - autoIndent: true - }); -} - -parseRestriction(); -newJSONEditor('restrictions'); -$('#restrictions').text(JSON.stringify(restrictions, null, 4)); - -function updateIcons() { - let r = []; - let res = $('#restrictions'); - try { - r = JSON.parse(res.val()); - res.removeClass('is-invalid'); - res.addClass('is-valid'); - } catch (e) { - res.removeClass('is-valid'); - res.addClass('is-invalid'); - return; - } - restrictions = r; - parseRestriction(); -} - function approve() { let data = { "oidc_iss": issuer, @@ -181,9 +45,3 @@ function cancel() { contentType : "application/json" }); } - -$('#cp-create_mytoken').click(function() { - let enabled = $(this).prop("checked"); - $('.subtoken-capability-check').prop("checked", enabled); - $('.subtoken-capability-check').prop("disabled", !enabled); -}); diff --git a/internal/server/web/static/js/countries.js b/internal/server/web/static/js/countries.js new file mode 100644 index 00000000..7105a9d3 --- /dev/null +++ b/internal/server/web/static/js/countries.js @@ -0,0 +1,742 @@ +const countries = { + "Afghanistan": { + "code": "AF" + }, + "Ã…land Islands": { + "code": "AX" + }, + "Albania": { + "code": "AL" + }, + "Algeria": { + "code": "DZ" + }, + "American Samoa": { + "code": "AS" + }, + "Andorra": { + "code": "AD" + }, + "Angola": { + "code": "AO" + }, + "Anguilla": { + "code": "AI" + }, + "Antarctica": { + "code": "AQ" + }, + "Antigua and Barbuda": { + "code": "AG" + }, + "Argentina": { + "code": "AR" + }, + "Armenia": { + "code": "AM" + }, + "Aruba": { + "code": "AW" + }, + "Australia": { + "code": "AU" + }, + "Austria": { + "code": "AT" + }, + "Azerbaijan": { + "code": "AZ" + }, + "Bahamas": { + "code": "BS" + }, + "Bahrain": { + "code": "BH" + }, + "Bangladesh": { + "code": "BD" + }, + "Barbados": { + "code": "BB" + }, + "Belarus": { + "code": "BY" + }, + "Belgium": { + "code": "BE" + }, + "Belize": { + "code": "BZ" + }, + "Benin": { + "code": "BJ" + }, + "Bermuda": { + "code": "BM" + }, + "Bhutan": { + "code": "BT" + }, + "Bolivia": { + "code": "BO" + }, + "Bosnia and Herzegovina": { + "code": "BA" + }, + "Botswana": { + "code": "BW" + }, + "Bouvet Island": { + "code": "BV" + }, + "Brazil": { + "code": "BR" + }, + "British Indian Ocean Territory": { + "code": "IO" + }, + "Brunei Darussalam": { + "code": "BN" + }, + "Bulgaria": { + "code": "BG" + }, + "Burkina Faso": { + "code": "BF" + }, + "Burundi": { + "code": "BI" + }, + "Cambodia": { + "code": "KH" + }, + "Cameroon": { + "code": "CM" + }, + "Canada": { + "code": "CA" + }, + "Cape Verde": { + "code": "CV" + }, + "Cayman Islands": { + "code": "KY" + }, + "Central African Republic": { + "code": "CF" + }, + "Chad": { + "code": "TD" + }, + "Chile": { + "code": "CL" + }, + "China": { + "code": "CN" + }, + "Christmas Island": { + "code": "CX" + }, + "Cocos (Keeling) Islands": { + "code": "CC" + }, + "Colombia": { + "code": "CO" + }, + "Comoros": { + "code": "KM" + }, + "Congo": { + "code": "CG" + }, + "Congo, The Democratic Republic of the": { + "code": "CD" + }, + "Cook Islands": { + "code": "CK" + }, + "Costa Rica": { + "code": "CR" + }, + "Cote D'Ivoire": { + "code": "CI" + }, + "Croatia": { + "code": "HR" + }, + "Cuba": { + "code": "CU" + }, + "Cyprus": { + "code": "CY" + }, + "Czech Republic": { + "code": "CZ" + }, + "Denmark": { + "code": "DK" + }, + "Djibouti": { + "code": "DJ" + }, + "Dominica": { + "code": "DM" + }, + "Dominican Republic": { + "code": "DO" + }, + "Ecuador": { + "code": "EC" + }, + "Egypt": { + "code": "EG" + }, + "El Salvador": { + "code": "SV" + }, + "Equatorial Guinea": { + "code": "GQ" + }, + "Eritrea": { + "code": "ER" + }, + "Estonia": { + "code": "EE" + }, + "Ethiopia": { + "code": "ET" + }, + "Falkland Islands (Malvinas)": { + "code": "FK" + }, + "Faroe Islands": { + "code": "FO" + }, + "Fiji": { + "code": "FJ" + }, + "Finland": { + "code": "FI" + }, + "France": { + "code": "FR" + }, + "French Guiana": { + "code": "GF" + }, + "French Polynesia": { + "code": "PF" + }, + "French Southern Territories": { + "code": "TF" + }, + "Gabon": { + "code": "GA" + }, + "Gambia": { + "code": "GM" + }, + "Georgia": { + "code": "GE" + }, + "Germany": { + "code": "DE" + }, + "Ghana": { + "code": "GH" + }, + "Gibraltar": { + "code": "GI" + }, + "Greece": { + "code": "GR" + }, + "Greenland": { + "code": "GL" + }, + "Grenada": { + "code": "GD" + }, + "Guadeloupe": { + "code": "GP" + }, + "Guam": { + "code": "GU" + }, + "Guatemala": { + "code": "GT" + }, + "Guernsey": { + "code": "GG" + }, + "Guinea": { + "code": "GN" + }, + "Guinea-Bissau": { + "code": "GW" + }, + "Guyana": { + "code": "GY" + }, + "Haiti": { + "code": "HT" + }, + "Heard Island and Mcdonald Islands": { + "code": "HM" + }, + "Holy See (Vatican City State)": { + "code": "VA" + }, + "Honduras": { + "code": "HN" + }, + "Hong Kong": { + "code": "HK" + }, + "Hungary": { + "code": "HU" + }, + "Iceland": { + "code": "IS" + }, + "India": { + "code": "IN" + }, + "Indonesia": { + "code": "ID" + }, + "Iran, Islamic Republic Of": { + "code": "IR" + }, + "Iraq": { + "code": "IQ" + }, + "Ireland": { + "code": "IE" + }, + "Isle of Man": { + "code": "IM" + }, + "Israel": { + "code": "IL" + }, + "Italy": { + "code": "IT" + }, + "Jamaica": { + "code": "JM" + }, + "Japan": { + "code": "JP" + }, + "Jersey": { + "code": "JE" + }, + "Jordan": { + "code": "JO" + }, + "Kazakhstan": { + "code": "KZ" + }, + "Kenya": { + "code": "KE" + }, + "Kiribati": { + "code": "KI" + }, + "Democratic People's Republic of Korea": { + "code": "KP" + }, + "Korea, Republic of": { + "code": "KR" + }, + "Kosovo": { + "code": "XK" + }, + "Kuwait": { + "code": "KW" + }, + "Kyrgyzstan": { + "code": "KG" + }, + "Lao People's Democratic Republic": { + "code": "LA" + }, + "Latvia": { + "code": "LV" + }, + "Lebanon": { + "code": "LB" + }, + "Lesotho": { + "code": "LS" + }, + "Liberia": { + "code": "LR" + }, + "Libyan Arab Jamahiriya": { + "code": "LY" + }, + "Liechtenstein": { + "code": "LI" + }, + "Lithuania": { + "code": "LT" + }, + "Luxembourg": { + "code": "LU" + }, + "Macao": { + "code": "MO" + }, + "Macedonia, The Former Yugoslav Republic of": { + "code": "MK" + }, + "Madagascar": { + "code": "MG" + }, + "Malawi": { + "code": "MW" + }, + "Malaysia": { + "code": "MY" + }, + "Maldives": { + "code": "MV" + }, + "Mali": { + "code": "ML" + }, + "Malta": { + "code": "MT" + }, + "Marshall Islands": { + "code": "MH" + }, + "Martinique": { + "code": "MQ" + }, + "Mauritania": { + "code": "MR" + }, + "Mauritius": { + "code": "MU" + }, + "Mayotte": { + "code": "YT" + }, + "Mexico": { + "code": "MX" + }, + "Micronesia, Federated States of": { + "code": "FM" + }, + "Moldova, Republic of": { + "code": "MD" + }, + "Monaco": { + "code": "MC" + }, + "Mongolia": { + "code": "MN" + }, + "Montenegro": { + "code": "ME" + }, + "Montserrat": { + "code": "MS" + }, + "Morocco": { + "code": "MA" + }, + "Mozambique": { + "code": "MZ" + }, + "Myanmar": { + "code": "MM" + }, + "Namibia": { + "code": "NA" + }, + "Nauru": { + "code": "NR" + }, + "Nepal": { + "code": "NP" + }, + "Netherlands": { + "code": "NL" + }, + "Netherlands Antilles": { + "code": "AN" + }, + "New Caledonia": { + "code": "NC" + }, + "New Zealand": { + "code": "NZ" + }, + "Nicaragua": { + "code": "NI" + }, + "Niger": { + "code": "NE" + }, + "Nigeria": { + "code": "NG" + }, + "Niue": { + "code": "NU" + }, + "Norfolk Island": { + "code": "NF" + }, + "Northern Mariana Islands": { + "code": "MP" + }, + "Norway": { + "code": "NO" + }, + "Oman": { + "code": "OM" + }, + "Pakistan": { + "code": "PK" + }, + "Palau": { + "code": "PW" + }, + "Palestinian Territory, Occupied": { + "code": "PS" + }, + "Panama": { + "code": "PA" + }, + "Papua New Guinea": { + "code": "PG" + }, + "Paraguay": { + "code": "PY" + }, + "Peru": { + "code": "PE" + }, + "Philippines": { + "code": "PH" + }, + "Pitcairn": { + "code": "PN" + }, + "Poland": { + "code": "PL" + }, + "Portugal": { + "code": "PT" + }, + "Puerto Rico": { + "code": "PR" + }, + "Qatar": { + "code": "QA" + }, + "Reunion": { + "code": "RE" + }, + "Romania": { + "code": "RO" + }, + "Russian Federation": { + "code": "RU" + }, + "Rwanda": { + "code": "RW" + }, + "Saint Helena": { + "code": "SH" + }, + "Saint Kitts and Nevis": { + "code": "KN" + }, + "Saint Lucia": { + "code": "LC" + }, + "Saint Pierre and Miquelon": { + "code": "PM" + }, + "Saint Vincent and the Grenadines": { + "code": "VC" + }, + "Samoa": { + "code": "WS" + }, + "San Marino": { + "code": "SM" + }, + "Sao Tome and Principe": { + "code": "ST" + }, + "Saudi Arabia": { + "code": "SA" + }, + "Senegal": { + "code": "SN" + }, + "Serbia": { + "code": "RS" + }, + "Seychelles": { + "code": "SC" + }, + "Sierra Leone": { + "code": "SL" + }, + "Singapore": { + "code": "SG" + }, + "Slovakia": { + "code": "SK" + }, + "Slovenia": { + "code": "SI" + }, + "Solomon Islands": { + "code": "SB" + }, + "Somalia": { + "code": "SO" + }, + "South Africa": { + "code": "ZA" + }, + "South Georgia and the South Sandwich Islands": { + "code": "GS" + }, + "Spain": { + "code": "ES" + }, + "Sri Lanka": { + "code": "LK" + }, + "Sudan": { + "code": "SD" + }, + "Suriname": { + "code": "SR" + }, + "Svalbard and Jan Mayen": { + "code": "SJ" + }, + "Swaziland": { + "code": "SZ" + }, + "Sweden": { + "code": "SE" + }, + "Switzerland": { + "code": "CH" + }, + "Syrian Arab Republic": { + "code": "SY" + }, + "Taiwan": { + "code": "TW" + }, + "Tajikistan": { + "code": "TJ" + }, + "Tanzania, United Republic of": { + "code": "TZ" + }, + "Thailand": { + "code": "TH" + }, + "Timor-Leste": { + "code": "TL" + }, + "Togo": { + "code": "TG" + }, + "Tokelau": { + "code": "TK" + }, + "Tonga": { + "code": "TO" + }, + "Trinidad and Tobago": { + "code": "TT" + }, + "Tunisia": { + "code": "TN" + }, + "Turkey": { + "code": "TR" + }, + "Turkmenistan": { + "code": "TM" + }, + "Turks and Caicos Islands": { + "code": "TC" + }, + "Tuvalu": { + "code": "TV" + }, + "Uganda": { + "code": "UG" + }, + "Ukraine": { + "code": "UA" + }, + "United Arab Emirates": { + "code": "AE" + }, + "United Kingdom": { + "code": "GB" + }, + "United States": { + "code": "US" + }, + "United States Minor Outlying Islands": { + "code": "UM" + }, + "Uruguay": { + "code": "UY" + }, + "Uzbekistan": { + "code": "UZ" + }, + "Vanuate": { + "code": "VU" + }, + "Venezuela": { + "code": "VE" + }, + "Viet Nam": { + "code": "VN" + }, + "Virgin Islands, British": { + "code": "VG" + }, + "Virgin Island, U.S.": { + "code": "VI" + }, + "Wallis and Futuna": { + "code": "WF" + }, + "Western Sahara": { + "code": "EH" + }, + "Yemen": { + "code": "YE" + }, + "Zambia": { + "code": "ZM" + }, + "Zimbabwe": { + "code": "ZW" + } +} + +let countriesByCode = {}; +for (const c in countries) { + countriesByCode[countries[c].code]=c; +} \ No newline at end of file diff --git a/internal/server/web/static/js/create-mt.js b/internal/server/web/static/js/create-mt.js new file mode 100644 index 00000000..2b34c6b3 --- /dev/null +++ b/internal/server/web/static/js/create-mt.js @@ -0,0 +1,201 @@ + +const subtokenCapabilityChecks = $('.subtoken-capability-check'); +const mtResult = $('#mt-result'); +const mtResultColor = $('#mt-result-color'); +const mtConfig = $('#mt-config'); +const pendingHeading = $('#mt-result-heading-pending'); +const pendingSpinner = $('#pending-spinner'); +const successHeading = $('#mt-result-heading-success'); +const errorHeading = $('#mt-result-heading-error'); +const mtResultMsg = $('#mt-result-msg'); +const copyButton = $('#mt-result-copy'); +const rotationAT = $('#rotationAT'); +const rotationOther = $('#rotationOther'); +const rotationLifetime = $('#rotationLifetime'); +const rotationAutoRevoke = $('#rotationAutoRevoke'); +const authURL = $('#authorization-url'); +const capabilityCreateMytoken = $('#cp-create_mytoken'); + + +$(function () { + $('#cp-AT').prop('checked', true) + $('#cp-tokeninfo_introspect').prop('checked', true) + if (capabilityCreateMytoken.prop("checked")) { + $('#subtokenCapabilities').showB(); + } + +}) + +capabilityCreateMytoken.on("click", function() { + let enabled = $(this).prop("checked"); + subtokenCapabilityChecks.prop("disabled", !enabled); + if (!enabled) { + subtokenCapabilityChecks.prop("checked", false); + } + $('#subtokenCapabilities').toggleClass('d-none'); +}); + + +$('#next-mt').on('click', function(e){ + window.clearInterval(intervalID); + mtResult.hideB(); + mtConfig.showB(); +}) + +$('#get-mt').on('click', function(e){ + let data = { + "name": $('#tokenName').val(), + "response_type": $('#shortToken').prop("checked") ? "short_token":"token", + "oidc_issuer": storageGet("oidc_issuer"), + "grant_type": "oidc_flow", + "oidc_flow": "authorization_code", + "redirect_type": "native", + "restrictions": restrictions, + "capabilities": $('.capability-check:checked').map(function(_, el) { + return $(el).val(); + }).get(), + "subtoken_capabilities": $('.subtoken-capability-check:checked').map(function(_, el) { + return $(el).val(); + }).get() + }; + if (rotationAT.prop("checked")||rotationOther.prop("checked")) { + data["rotation"] = { + "on_AT": rotationAT.prop("checked"), + "on_other": rotationOther.prop("checked"), + "lifetime": Number(rotationLifetime.val()), + "auto_revoke": rotationAutoRevoke.prop("checked") + }; + } + data = JSON.stringify(data); + console.log(data); + $.ajax({ + type: "POST", + url: storageGet('mytoken_endpoint'), + data: data, + success: function (res){ + let url = res['authorization_url']; + let code = res['polling_code']; + let interval = res['polling_code_interval']; + authURL.attr("href", url); + authURL.text(url); + polling(code, interval) + }, + error: function(errRes){ + let errMsg = getErrorMessage(errRes); + showError(errMsg); + }, + dataType: "json", + contentType : "application/json" + }); + showPending(); + mtResult.showB(); + mtConfig.hideB(); +}) + +function showPending() { + pendingHeading.showB(); + pendingSpinner.showB(); + successHeading.hideB(); + errorHeading.hideB(); + copyButton.hideB(); + mtResultMsg.text(''); + mtResultColor.addClass('alert-warning'); + mtResultColor.removeClass('alert-success', 'alert-danger'); +} +function showSuccess(msg) { + pendingHeading.hideB(); + pendingSpinner.hideB(); + successHeading.showB(); + errorHeading.hideB(); + copyButton.showB(); + mtResultMsg.text(msg); + mtResultColor.addClass('alert-success'); + mtResultColor.removeClass('alert-warning', 'alert-danger'); +} +function showError(msg) { + pendingHeading.hideB(); + pendingSpinner.hideB(); + successHeading.hideB(); + errorHeading.showB(); + copyButton.showB(); + mtResultMsg.text(msg); + mtResultColor.addClass('alert-danger'); + mtResultColor.removeClass('alert-success', 'alert-warning'); +} + +var intervalID; + +function polling(code, interval) { + interval = interval ? interval*1000 : 5000; + let data = { + "grant_type": "polling_code", + "polling_code": code, + } + data = JSON.stringify(data); + intervalID = window.setInterval(function (){ + $.ajax({ + type: "POST", + url: storageGet("mytoken_endpoint"), + data: data, + success: function(res) { + showSuccess(res['mytoken']); + window.clearInterval(intervalID); + }, + error: function(errRes) { + let error = errRes.responseJSON['error']; + switch (error) { + case "authorization_pending": + message = "Authorization still pending."; + showPending(); + return; + case "access_denied": + message = "You denied the authorization request."; + window.clearInterval(intervalID); + break; + case "expired_token": + message = "Code expired. You might want to restart the flow."; + window.clearInterval(intervalID); + break; + case "invalid_grant": + case "invalid_token": + message = "Code already used."; + window.clearInterval(intervalID); + break; + case "undefined": + message = "No response from server"; + window.clearInterval(intervalID); + break; + default: + message = getErrorMessage(errRes); + window.clearInterval(intervalID); + break; + } + showError(message) + } + }); + }, interval); +} + + +function checkRotation() { + let atEnabled = rotationAT.prop("checked"); + let otherEnabled = rotationOther.prop("checked"); + let rotationEnabled = atEnabled||otherEnabled; + rotationLifetime.prop("disabled", !rotationEnabled); + rotationAutoRevoke.prop("disabled", !rotationEnabled); + return [atEnabled, otherEnabled]; +} + +rotationAT.on("click", function() { + let en = checkRotation(); + if (en[0]&&!en[1]) { + rotationAutoRevoke.prop("checked", true); + } +}); + +rotationOther.on("click", function() { + let en = checkRotation(); + if (en[1]&&!en[0]) { + rotationAutoRevoke.prop("checked", true); + } +}); diff --git a/internal/server/web/static/js/discovery.js b/internal/server/web/static/js/discovery.js index 93d0358a..04e84056 100644 --- a/internal/server/web/static/js/discovery.js +++ b/internal/server/web/static/js/discovery.js @@ -4,7 +4,8 @@ const configElements = [ "mytoken_endpoint", "usersettings_endpoint", "revocation_endpoint", - "tokeninfo_endpoint" + "tokeninfo_endpoint", + "providers_supported" ] function discovery() { diff --git a/internal/server/web/static/js/lib/bootstrap4-toggle.min.js b/internal/server/web/static/js/lib/bootstrap4-toggle.min.js new file mode 100644 index 00000000..3839319e --- /dev/null +++ b/internal/server/web/static/js/lib/bootstrap4-toggle.min.js @@ -0,0 +1,11 @@ +/*\ +|*| ======================================================================== +|*| Bootstrap Toggle: bootstrap4-toggle.js v3.6.1 +|*| https://gitbrent.github.io/bootstrap4-toggle/ +|*| ======================================================================== +|*| Copyright 2018-2019 Brent Ely +|*| Licensed under MIT +|*| ======================================================================== +\*/ +!function(a){"use strict";function l(t,e){this.$element=a(t),this.options=a.extend({},this.defaults(),e),this.render()}l.VERSION="3.6.0",l.DEFAULTS={on:"On",off:"Off",onstyle:"primary",offstyle:"light",size:"normal",style:"",width:null,height:null},l.prototype.defaults=function(){return{on:this.$element.attr("data-on")||l.DEFAULTS.on,off:this.$element.attr("data-off")||l.DEFAULTS.off,onstyle:this.$element.attr("data-onstyle")||l.DEFAULTS.onstyle,offstyle:this.$element.attr("data-offstyle")||l.DEFAULTS.offstyle,size:this.$element.attr("data-size")||l.DEFAULTS.size,style:this.$element.attr("data-style")||l.DEFAULTS.style,width:this.$element.attr("data-width")||l.DEFAULTS.width,height:this.$element.attr("data-height")||l.DEFAULTS.height}},l.prototype.render=function(){this._onstyle="btn-"+this.options.onstyle,this._offstyle="btn-"+this.options.offstyle;var t="large"===this.options.size||"lg"===this.options.size?"btn-lg":"small"===this.options.size||"sm"===this.options.size?"btn-sm":"mini"===this.options.size||"xs"===this.options.size?"btn-xs":"",e=a('