From 3e1e0b604fb42eba4617d77a164cca37d4cae1aa Mon Sep 17 00:00:00 2001 From: Patrick Date: Sun, 8 Mar 2020 03:45:19 +0100 Subject: [PATCH] Add generic OpenID Connect provider (OAuth2) This adds the oauth2 provider `oidc`. It needs an additional argument, the OIDC discovery endpoint to figure out where the auth and token URLs are. Configuration is similar to setting up the Google Authentication with these changes: * `OAUTH2_PROVIDER = oidc` * `OAUTH2_OIDC_DISCOVERY_ENDPOINT = https://auth.exampe.org/discovery` --- config/config_test.go | 35 + config/options.go | 225 ++- config/parser.go | 2 + go.mod | 8 +- go.sum | 42 +- locale/translations.go | 52 +- locale/translations/de_DE.json | 5 +- locale/translations/en_US.json | 3 + locale/translations/es_ES.json | 3 + locale/translations/fr_FR.json | 3 + locale/translations/it_IT.json | 3 + locale/translations/ja_JP.json | 3 + locale/translations/nl_NL.json | 3 + locale/translations/pl_PL.json | 3 + locale/translations/ru_RU.json | 3 + locale/translations/zh_CN.json | 3 + oauth2/google.go | 3 +- oauth2/manager.go | 17 +- oauth2/oidc.go | 61 + oauth2/provider.go | 3 +- template/html/login.html | 4 + template/html/settings.html | 8 + template/views.go | 16 +- ui/oauth2.go | 5 +- ui/oauth2_callback.go | 4 +- ui/oauth2_redirect.go | 2 +- ui/oauth2_unlink.go | 2 +- ui/static/css.go | 24 +- vendor/github.com/coreos/go-oidc/.gitignore | 2 + vendor/github.com/coreos/go-oidc/.travis.yml | 16 + .../github.com/coreos/go-oidc/CONTRIBUTING.md | 71 + vendor/github.com/coreos/go-oidc/DCO | 36 + vendor/github.com/coreos/go-oidc/LICENSE | 202 ++ vendor/github.com/coreos/go-oidc/MAINTAINERS | 3 + vendor/github.com/coreos/go-oidc/NOTICE | 5 + vendor/github.com/coreos/go-oidc/README.md | 72 + .../coreos/go-oidc/code-of-conduct.md | 61 + vendor/github.com/coreos/go-oidc/jose.go | 20 + vendor/github.com/coreos/go-oidc/jwks.go | 228 +++ vendor/github.com/coreos/go-oidc/oidc.go | 409 ++++ vendor/github.com/coreos/go-oidc/test | 16 + vendor/github.com/coreos/go-oidc/verify.go | 336 +++ .../pquerna/cachecontrol/.travis.yml | 10 + .../github.com/pquerna/cachecontrol/LICENSE | 202 ++ .../github.com/pquerna/cachecontrol/README.md | 107 + vendor/github.com/pquerna/cachecontrol/api.go | 48 + .../cachecontrol/cacheobject/directive.go | 546 +++++ .../pquerna/cachecontrol/cacheobject/lex.go | 93 + .../cachecontrol/cacheobject/object.go | 387 ++++ .../cachecontrol/cacheobject/reasons.go | 95 + .../cachecontrol/cacheobject/warning.go | 107 + vendor/github.com/pquerna/cachecontrol/doc.go | 25 + vendor/golang.org/x/crypto/ed25519/ed25519.go | 222 ++ .../x/crypto/ed25519/ed25519_go113.go | 73 + .../ed25519/internal/edwards25519/const.go | 1422 +++++++++++++ .../internal/edwards25519/edwards25519.go | 1793 +++++++++++++++++ vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go | 77 + .../square/go-jose.v2/.gitcookies.sh.enc | 1 + vendor/gopkg.in/square/go-jose.v2/.gitignore | 7 + vendor/gopkg.in/square/go-jose.v2/.travis.yml | 44 + .../gopkg.in/square/go-jose.v2/BUG-BOUNTY.md | 10 + .../square/go-jose.v2/CONTRIBUTING.md | 14 + vendor/gopkg.in/square/go-jose.v2/LICENSE | 202 ++ vendor/gopkg.in/square/go-jose.v2/README.md | 118 ++ .../gopkg.in/square/go-jose.v2/asymmetric.go | 592 ++++++ .../square/go-jose.v2/cipher/cbc_hmac.go | 196 ++ .../square/go-jose.v2/cipher/concat_kdf.go | 75 + .../square/go-jose.v2/cipher/ecdh_es.go | 86 + .../square/go-jose.v2/cipher/key_wrap.go | 109 + vendor/gopkg.in/square/go-jose.v2/crypter.go | 541 +++++ vendor/gopkg.in/square/go-jose.v2/doc.go | 27 + vendor/gopkg.in/square/go-jose.v2/encoding.go | 185 ++ .../gopkg.in/square/go-jose.v2/json/LICENSE | 27 + .../gopkg.in/square/go-jose.v2/json/README.md | 13 + .../gopkg.in/square/go-jose.v2/json/decode.go | 1183 +++++++++++ .../gopkg.in/square/go-jose.v2/json/encode.go | 1197 +++++++++++ .../gopkg.in/square/go-jose.v2/json/indent.go | 141 ++ .../square/go-jose.v2/json/scanner.go | 623 ++++++ .../gopkg.in/square/go-jose.v2/json/stream.go | 480 +++++ .../gopkg.in/square/go-jose.v2/json/tags.go | 44 + vendor/gopkg.in/square/go-jose.v2/jwe.go | 294 +++ vendor/gopkg.in/square/go-jose.v2/jwk.go | 608 ++++++ vendor/gopkg.in/square/go-jose.v2/jws.go | 366 ++++ vendor/gopkg.in/square/go-jose.v2/opaque.go | 144 ++ vendor/gopkg.in/square/go-jose.v2/shared.go | 520 +++++ vendor/gopkg.in/square/go-jose.v2/signing.go | 441 ++++ .../gopkg.in/square/go-jose.v2/symmetric.go | 482 +++++ vendor/modules.txt | 12 + 88 files changed, 15856 insertions(+), 155 deletions(-) create mode 100644 oauth2/oidc.go create mode 100644 vendor/github.com/coreos/go-oidc/.gitignore create mode 100644 vendor/github.com/coreos/go-oidc/.travis.yml create mode 100644 vendor/github.com/coreos/go-oidc/CONTRIBUTING.md create mode 100644 vendor/github.com/coreos/go-oidc/DCO create mode 100644 vendor/github.com/coreos/go-oidc/LICENSE create mode 100644 vendor/github.com/coreos/go-oidc/MAINTAINERS create mode 100644 vendor/github.com/coreos/go-oidc/NOTICE create mode 100644 vendor/github.com/coreos/go-oidc/README.md create mode 100644 vendor/github.com/coreos/go-oidc/code-of-conduct.md create mode 100644 vendor/github.com/coreos/go-oidc/jose.go create mode 100644 vendor/github.com/coreos/go-oidc/jwks.go create mode 100644 vendor/github.com/coreos/go-oidc/oidc.go create mode 100644 vendor/github.com/coreos/go-oidc/test create mode 100644 vendor/github.com/coreos/go-oidc/verify.go create mode 100644 vendor/github.com/pquerna/cachecontrol/.travis.yml create mode 100644 vendor/github.com/pquerna/cachecontrol/LICENSE create mode 100644 vendor/github.com/pquerna/cachecontrol/README.md create mode 100644 vendor/github.com/pquerna/cachecontrol/api.go create mode 100644 vendor/github.com/pquerna/cachecontrol/cacheobject/directive.go create mode 100644 vendor/github.com/pquerna/cachecontrol/cacheobject/lex.go create mode 100644 vendor/github.com/pquerna/cachecontrol/cacheobject/object.go create mode 100644 vendor/github.com/pquerna/cachecontrol/cacheobject/reasons.go create mode 100644 vendor/github.com/pquerna/cachecontrol/cacheobject/warning.go create mode 100644 vendor/github.com/pquerna/cachecontrol/doc.go create mode 100644 vendor/golang.org/x/crypto/ed25519/ed25519.go create mode 100644 vendor/golang.org/x/crypto/ed25519/ed25519_go113.go create mode 100644 vendor/golang.org/x/crypto/ed25519/internal/edwards25519/const.go create mode 100644 vendor/golang.org/x/crypto/ed25519/internal/edwards25519/edwards25519.go create mode 100644 vendor/golang.org/x/crypto/pbkdf2/pbkdf2.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/.gitcookies.sh.enc create mode 100644 vendor/gopkg.in/square/go-jose.v2/.gitignore create mode 100644 vendor/gopkg.in/square/go-jose.v2/.travis.yml create mode 100644 vendor/gopkg.in/square/go-jose.v2/BUG-BOUNTY.md create mode 100644 vendor/gopkg.in/square/go-jose.v2/CONTRIBUTING.md create mode 100644 vendor/gopkg.in/square/go-jose.v2/LICENSE create mode 100644 vendor/gopkg.in/square/go-jose.v2/README.md create mode 100644 vendor/gopkg.in/square/go-jose.v2/asymmetric.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/cipher/cbc_hmac.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/cipher/concat_kdf.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/cipher/ecdh_es.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/cipher/key_wrap.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/crypter.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/doc.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/encoding.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/json/LICENSE create mode 100644 vendor/gopkg.in/square/go-jose.v2/json/README.md create mode 100644 vendor/gopkg.in/square/go-jose.v2/json/decode.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/json/encode.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/json/indent.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/json/scanner.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/json/stream.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/json/tags.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/jwe.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/jwk.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/jws.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/opaque.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/shared.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/signing.go create mode 100644 vendor/gopkg.in/square/go-jose.v2/symmetric.go diff --git a/config/config_test.go b/config/config_test.go index ad4ce6cf..90bfaf19 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -832,6 +832,41 @@ func TestDefaultOAuth2RedirectURLValue(t *testing.T) { } } +func TestOAuth2OidcDiscoveryEndpoint(t *testing.T) { + os.Clearenv() + os.Setenv("OAUTH2_OIDC_DISCOVERY_ENDPOINT", "http://example.org") + + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() + if err != nil { + t.Fatalf(`Parsing failure: %v`, err) + } + + expected := "http://example.org" + result := opts.OAuth2OidcDiscoveryEndpoint() + + if result != expected { + t.Fatalf(`Unexpected OAUTH2_OIDC_DISCOVERY_ENDPOINT value, got %q instead of %q`, result, expected) + } +} + +func TestDefaultOAuth2OidcDiscoveryEndpointValue(t *testing.T) { + os.Clearenv() + + parser := NewParser() + opts, err := parser.ParseEnvironmentVariables() + if err != nil { + t.Fatalf(`Parsing failure: %v`, err) + } + + expected := defaultOAuth2OidcDiscoveryEndpoint + result := opts.OAuth2OidcDiscoveryEndpoint() + + if result != expected { + t.Fatalf(`Unexpected OAUTH2_REDIRECT_URL value, got %q instead of %q`, result, expected) + } +} + func TestOAuth2Provider(t *testing.T) { os.Clearenv() os.Setenv("OAUTH2_PROVIDER", "google") diff --git a/config/options.go b/config/options.go index 3e817162..73cdbd8f 100644 --- a/config/options.go +++ b/config/options.go @@ -10,123 +10,126 @@ import ( ) const ( - defaultHTTPS = false - defaultLogDateTime = false - defaultHSTS = true - defaultHTTPService = true - defaultSchedulerService = true - defaultDebug = false - defaultBaseURL = "http://localhost" - defaultRootURL = "http://localhost" - defaultBasePath = "" - defaultWorkerPoolSize = 5 - defaultPollingFrequency = 60 - defaultBatchSize = 10 - defaultRunMigrations = false - defaultDatabaseURL = "user=postgres password=postgres dbname=miniflux2 sslmode=disable" - defaultDatabaseMaxConns = 20 - defaultDatabaseMinConns = 1 - defaultListenAddr = "127.0.0.1:8080" - defaultCertFile = "" - defaultKeyFile = "" - defaultCertDomain = "" - defaultCertCache = "/tmp/cert_cache" - defaultCleanupFrequencyHours = 24 - defaultCleanupArchiveReadDays = 60 - defaultCleanupRemoveSessionsDays = 30 - defaultProxyImages = "http-only" - defaultCreateAdmin = false - defaultOAuth2UserCreation = false - defaultOAuth2ClientID = "" - defaultOAuth2ClientSecret = "" - defaultOAuth2RedirectURL = "" - defaultOAuth2Provider = "" - defaultPocketConsumerKey = "" - defaultHTTPClientTimeout = 20 - defaultHTTPClientMaxBodySize = 15 - defaultAuthProxyHeader = "" - defaultAuthProxyUserCreation = false + defaultHTTPS = false + defaultLogDateTime = false + defaultHSTS = true + defaultHTTPService = true + defaultSchedulerService = true + defaultDebug = false + defaultBaseURL = "http://localhost" + defaultRootURL = "http://localhost" + defaultBasePath = "" + defaultWorkerPoolSize = 5 + defaultPollingFrequency = 60 + defaultBatchSize = 10 + defaultRunMigrations = false + defaultDatabaseURL = "user=postgres password=postgres dbname=miniflux2 sslmode=disable" + defaultDatabaseMaxConns = 20 + defaultDatabaseMinConns = 1 + defaultListenAddr = "127.0.0.1:8080" + defaultCertFile = "" + defaultKeyFile = "" + defaultCertDomain = "" + defaultCertCache = "/tmp/cert_cache" + defaultCleanupFrequencyHours = 24 + defaultCleanupArchiveReadDays = 60 + defaultCleanupRemoveSessionsDays = 30 + defaultProxyImages = "http-only" + defaultCreateAdmin = false + defaultOAuth2UserCreation = false + defaultOAuth2ClientID = "" + defaultOAuth2ClientSecret = "" + defaultOAuth2RedirectURL = "" + defaultOAuth2OidcDiscoveryEndpoint = "" + defaultOAuth2Provider = "" + defaultPocketConsumerKey = "" + defaultHTTPClientTimeout = 20 + defaultHTTPClientMaxBodySize = 15 + defaultAuthProxyHeader = "" + defaultAuthProxyUserCreation = false ) // Options contains configuration options. type Options struct { - HTTPS bool - logDateTime bool - hsts bool - httpService bool - schedulerService bool - debug bool - baseURL string - rootURL string - basePath string - databaseURL string - databaseMaxConns int - databaseMinConns int - runMigrations bool - listenAddr string - certFile string - certDomain string - certCache string - certKeyFile string - cleanupFrequencyHours int - cleanupArchiveReadDays int - cleanupRemoveSessionsDays int - pollingFrequency int - batchSize int - workerPoolSize int - createAdmin bool - proxyImages string - oauth2UserCreationAllowed bool - oauth2ClientID string - oauth2ClientSecret string - oauth2RedirectURL string - oauth2Provider string - pocketConsumerKey string - httpClientTimeout int - httpClientMaxBodySize int64 - authProxyHeader string - authProxyUserCreation bool + HTTPS bool + logDateTime bool + hsts bool + httpService bool + schedulerService bool + debug bool + baseURL string + rootURL string + basePath string + databaseURL string + databaseMaxConns int + databaseMinConns int + runMigrations bool + listenAddr string + certFile string + certDomain string + certCache string + certKeyFile string + cleanupFrequencyHours int + cleanupArchiveReadDays int + cleanupRemoveSessionsDays int + pollingFrequency int + batchSize int + workerPoolSize int + createAdmin bool + proxyImages string + oauth2UserCreationAllowed bool + oauth2ClientID string + oauth2ClientSecret string + oauth2RedirectURL string + oauth2OidcDiscoveryEndpoint string + oauth2Provider string + pocketConsumerKey string + httpClientTimeout int + httpClientMaxBodySize int64 + authProxyHeader string + authProxyUserCreation bool } // NewOptions returns Options with default values. func NewOptions() *Options { return &Options{ - HTTPS: defaultHTTPS, - logDateTime: defaultLogDateTime, - hsts: defaultHSTS, - httpService: defaultHTTPService, - schedulerService: defaultSchedulerService, - debug: defaultDebug, - baseURL: defaultBaseURL, - rootURL: defaultRootURL, - basePath: defaultBasePath, - databaseURL: defaultDatabaseURL, - databaseMaxConns: defaultDatabaseMaxConns, - databaseMinConns: defaultDatabaseMinConns, - runMigrations: defaultRunMigrations, - listenAddr: defaultListenAddr, - certFile: defaultCertFile, - certDomain: defaultCertDomain, - certCache: defaultCertCache, - certKeyFile: defaultKeyFile, - cleanupFrequencyHours: defaultCleanupFrequencyHours, - cleanupArchiveReadDays: defaultCleanupArchiveReadDays, - cleanupRemoveSessionsDays: defaultCleanupRemoveSessionsDays, - pollingFrequency: defaultPollingFrequency, - batchSize: defaultBatchSize, - workerPoolSize: defaultWorkerPoolSize, - createAdmin: defaultCreateAdmin, - proxyImages: defaultProxyImages, - oauth2UserCreationAllowed: defaultOAuth2UserCreation, - oauth2ClientID: defaultOAuth2ClientID, - oauth2ClientSecret: defaultOAuth2ClientSecret, - oauth2RedirectURL: defaultOAuth2RedirectURL, - oauth2Provider: defaultOAuth2Provider, - pocketConsumerKey: defaultPocketConsumerKey, - httpClientTimeout: defaultHTTPClientTimeout, - httpClientMaxBodySize: defaultHTTPClientMaxBodySize * 1024 * 1024, - authProxyHeader: defaultAuthProxyHeader, - authProxyUserCreation: defaultAuthProxyUserCreation, + HTTPS: defaultHTTPS, + logDateTime: defaultLogDateTime, + hsts: defaultHSTS, + httpService: defaultHTTPService, + schedulerService: defaultSchedulerService, + debug: defaultDebug, + baseURL: defaultBaseURL, + rootURL: defaultRootURL, + basePath: defaultBasePath, + databaseURL: defaultDatabaseURL, + databaseMaxConns: defaultDatabaseMaxConns, + databaseMinConns: defaultDatabaseMinConns, + runMigrations: defaultRunMigrations, + listenAddr: defaultListenAddr, + certFile: defaultCertFile, + certDomain: defaultCertDomain, + certCache: defaultCertCache, + certKeyFile: defaultKeyFile, + cleanupFrequencyHours: defaultCleanupFrequencyHours, + cleanupArchiveReadDays: defaultCleanupArchiveReadDays, + cleanupRemoveSessionsDays: defaultCleanupRemoveSessionsDays, + pollingFrequency: defaultPollingFrequency, + batchSize: defaultBatchSize, + workerPoolSize: defaultWorkerPoolSize, + createAdmin: defaultCreateAdmin, + proxyImages: defaultProxyImages, + oauth2UserCreationAllowed: defaultOAuth2UserCreation, + oauth2ClientID: defaultOAuth2ClientID, + oauth2ClientSecret: defaultOAuth2ClientSecret, + oauth2RedirectURL: defaultOAuth2RedirectURL, + oauth2OidcDiscoveryEndpoint: defaultOAuth2OidcDiscoveryEndpoint, + oauth2Provider: defaultOAuth2Provider, + pocketConsumerKey: defaultPocketConsumerKey, + httpClientTimeout: defaultHTTPClientTimeout, + httpClientMaxBodySize: defaultHTTPClientMaxBodySize * 1024 * 1024, + authProxyHeader: defaultAuthProxyHeader, + authProxyUserCreation: defaultAuthProxyUserCreation, } } @@ -250,6 +253,11 @@ func (o *Options) OAuth2RedirectURL() string { return o.oauth2RedirectURL } +// OAuth2OidcDiscoveryEndpoint returns the OAuth2 OIDC discovery endpoint. +func (o *Options) OAuth2OidcDiscoveryEndpoint() string { + return o.oauth2OidcDiscoveryEndpoint +} + // OAuth2Provider returns the name of the OAuth2 provider configured. func (o *Options) OAuth2Provider() string { return o.oauth2Provider @@ -348,6 +356,7 @@ func (o *Options) String() string { builder.WriteString(fmt.Sprintf("OAUTH2_CLIENT_ID: %v\n", o.oauth2ClientID)) builder.WriteString(fmt.Sprintf("OAUTH2_CLIENT_SECRET: %v\n", o.oauth2ClientSecret)) builder.WriteString(fmt.Sprintf("OAUTH2_REDIRECT_URL: %v\n", o.oauth2RedirectURL)) + builder.WriteString(fmt.Sprintf("OAUTH2_OIDC_DISCOVERY_ENDPOINT: %v\n", o.oauth2OidcDiscoveryEndpoint)) builder.WriteString(fmt.Sprintf("OAUTH2_PROVIDER: %v\n", o.oauth2Provider)) builder.WriteString(fmt.Sprintf("HTTP_CLIENT_TIMEOUT: %v\n", o.httpClientTimeout)) builder.WriteString(fmt.Sprintf("HTTP_CLIENT_MAX_BODY_SIZE: %v\n", o.httpClientMaxBodySize)) diff --git a/config/parser.go b/config/parser.go index 4011c56c..0f96a974 100644 --- a/config/parser.go +++ b/config/parser.go @@ -152,6 +152,8 @@ func (p *Parser) parseLines(lines []string) (err error) { p.opts.oauth2ClientSecret = parseString(value, defaultOAuth2ClientSecret) case "OAUTH2_REDIRECT_URL": p.opts.oauth2RedirectURL = parseString(value, defaultOAuth2RedirectURL) + case "OAUTH2_OIDC_DISCOVERY_ENDPOINT": + p.opts.oauth2OidcDiscoveryEndpoint = parseString(value, defaultOAuth2OidcDiscoveryEndpoint) case "OAUTH2_PROVIDER": p.opts.oauth2Provider = parseString(value, defaultOAuth2Provider) case "HTTP_CLIENT_TIMEOUT": diff --git a/go.mod b/go.mod index b9241fbc..ed7a413c 100644 --- a/go.mod +++ b/go.mod @@ -4,15 +4,21 @@ module miniflux.app require ( github.com/PuerkitoBio/goquery v1.5.0 + github.com/coreos/go-oidc v2.2.1+incompatible github.com/golang/protobuf v1.3.2 // indirect github.com/gorilla/mux v1.7.3 github.com/lib/pq v1.2.0 - github.com/tdewolff/minify/v2 v2.5.2 // indirect + github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect + github.com/stretchr/testify v1.4.0 // indirect + github.com/tdewolff/minify/v2 v2.7.2 // indirect golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708 + golang.org/x/lint v0.0.0-20200130185559-910be7a94367 // indirect golang.org/x/net v0.0.0-20191112182307-2180aed22343 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 golang.org/x/sys v0.0.0-20190904154756-749cb33beabd // indirect + golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 // indirect google.golang.org/appengine v1.6.2 // indirect + gopkg.in/square/go-jose.v2 v2.4.1 // indirect ) go 1.11 diff --git a/go.sum b/go.sum index 78d9ea26..99fd295b 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,10 @@ github.com/PuerkitoBio/goquery v1.5.0/go.mod h1:qD2PgZ9lccMbQlc7eEOjaeRlFQON7xY8 github.com/andybalholm/cascadia v1.0.0 h1:hOCXnnZ5A+3eVDX8pvgl4kofXv2ELss0bKcqRySc45o= github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= +github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= +github.com/coreos/go-oidc v2.2.1+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= @@ -16,20 +20,28 @@ github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU= +github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo= -github.com/tdewolff/minify/v2 v2.5.2 h1:If/q1brvT+91oWiWnIMEGuFcwWtpB6AtLTxba78tvMs= -github.com/tdewolff/minify/v2 v2.5.2/go.mod h1:Q6mWHrmspbdRX0ZuUUoKIT8bDjVVXpIJ73ux7p7HZGg= -github.com/tdewolff/parse/v2 v2.3.9 h1:d8/K6XOLy5JVpLTG9Kx+SxA72rlm5OowFmVSVgtOlmM= -github.com/tdewolff/parse/v2 v2.3.9/go.mod h1:HansaqmN4I/U7L6/tUp0NcwT2tFO0F4EAWYGSDzkYNk= -github.com/tdewolff/test v1.0.0/go.mod h1:DiQUlutnqlEvdvhSn2LPGy4TFwRauAaYDsL+683RNX4= -github.com/tdewolff/test v1.0.4/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +github.com/tdewolff/minify/v2 v2.7.2 h1:XA92QuWsrKji+TlBv03mPuzUpSWz97mE5nyISY84wEY= +github.com/tdewolff/minify/v2 v2.7.2/go.mod h1:BkDSm8aMMT0ALGmpt7j3Ra7nLUgZL0qhyrAHXwxcy5w= +github.com/tdewolff/parse/v2 v2.4.2 h1:Bu2Qv6wepkc+Ou7iB/qHjAhEImlAP5vedzlQRUdj3BI= +github.com/tdewolff/parse/v2 v2.4.2/go.mod h1:WzaJpRSbwq++EIQHYIRTpbYKNA3gn9it1Ik++q4zyho= +github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472 h1:Gv7RPwsi3eZ2Fgewe3CBsuOebPwO27PoXzRpJPsvSSM= -golang.org/x/crypto v0.0.0-20190829043050-9756ffdc2472/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708 h1:pXVtWnwHkrWD9ru3sDxY/qFK/bfc0egRovX91EjWjf4= golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367 h1:0IiAsCRByjO2QjX7ZPkw5oU9x+n1YqRL802rjC0c3Aw= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -37,8 +49,7 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191112182307-2180aed22343 h1:00ohfJ4K98s3m6BGUoBd8nyfp4Yl0GoIKvw5abItTjI= golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -60,7 +71,18 @@ golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2 h1:0sfSpGSa544Fwnbot3Oxq/U6SXqjty6Jy/3wRhVS7ig= +golang.org/x/tools v0.0.0-20200216192241-b320d3a0f5a2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.2 h1:j8RI1yW0SkI+paT6uGwMlrMI/6zwYA6/CFil8rxOzGI= google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/square/go-jose.v2 v2.4.1 h1:H0TmLt7/KmzlrDOpa1F+zr0Tk90PbJYBfsVUmRLrf9Y= +gopkg.in/square/go-jose.v2 v2.4.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/locale/translations.go b/locale/translations.go index ca350e07..2acbc1ab 100644 --- a/locale/translations.go +++ b/locale/translations.go @@ -159,9 +159,12 @@ var translations = map[string]string{ "page.users.is_admin": "Administrator", "page.settings.title": "Einstellungen", "page.settings.link_google_account": "Google Konto verknüpfen", - "page.settings.unlink_google_account": "Diese Kategorie existiert nicht für diesen Benutzer", + "page.settings.unlink_google_account": "Google Konto Verknüpfung entfernen", + "page.settings.link_oidc_account": "OpenID Connect Konto verknüpfen", + "page.settings.unlink_oidc_account": "OpenID Connect Konto Verknüpfung entfernen", "page.login.title": "Anmeldung", "page.login.google_signin": "Anmeldung mit Google", + "page.login.oidc_signin": "Anmeldung mit OpenID Connect", "page.integrations.title": "Dienste", "page.integration.miniflux_api": "Miniflux API", "page.integration.miniflux_api_endpoint": "API Endpunkt", @@ -483,8 +486,11 @@ var translations = map[string]string{ "page.settings.title": "Settings", "page.settings.link_google_account": "Link my Google account", "page.settings.unlink_google_account": "Unlink my Google account", + "page.settings.link_oidc_account": "Link my OpenID Connect account", + "page.settings.unlink_oidc_account": "Unlink my OpenID Connect account", "page.login.title": "Sign In", "page.login.google_signin": "Sign in with Google", + "page.login.oidc_signin": "Sign in with OpenID Connect", "page.integrations.title": "Integrations", "page.integration.miniflux_api": "Miniflux API", "page.integration.miniflux_api_endpoint": "API Endpoint", @@ -786,8 +792,11 @@ var translations = map[string]string{ "page.settings.title": "Ajustes", "page.settings.link_google_account": "Vincular mi cuenta de Google", "page.settings.unlink_google_account": "Desvincular mi cuenta de Google", + "page.settings.link_oidc_account": "Vincular mi cuenta de OpenID Connect", + "page.settings.unlink_oidc_account": "Desvincular mi cuenta de OpenID Connect", "page.login.title": "Iniciar sesión", "page.login.google_signin": "Iniciar sesión con tu cuenta de Google", + "page.login.oidc_signin": "Iniciar sesión con tu cuenta de OpenID Connect", "page.integrations.title": "Integraciones", "page.integration.miniflux_api": "API de Miniflux", "page.integration.miniflux_api_endpoint": "Extremo de API", @@ -1089,8 +1098,11 @@ var translations = map[string]string{ "page.settings.title": "Réglages", "page.settings.link_google_account": "Associer mon compte Google", "page.settings.unlink_google_account": "Dissocier mon compte Google", + "page.settings.link_oidc_account": "Associer mon compte OpenID Connect", + "page.settings.unlink_oidc_account": "Dissocier mon compte OpenID Connect", "page.login.title": "Connexion", "page.login.google_signin": "Se connecter avec Google", + "page.login.oidc_signin": "Se connecter avec OpenID Connect", "page.integrations.title": "Intégrations", "page.integration.miniflux_api": "API de Miniflux", "page.integration.miniflux_api_endpoint": "Point de terminaison de l'API", @@ -1412,8 +1424,11 @@ var translations = map[string]string{ "page.settings.title": "Impostazioni", "page.settings.link_google_account": "Collega il mio account Google", "page.settings.unlink_google_account": "Scollega il mio account Google", + "page.settings.link_oidc_account": "Collega il mio account OpenID Connect", + "page.settings.unlink_oidc_account": "Scollega il mio account OpenID Connect", "page.login.title": "Accedi", "page.login.google_signin": "Accedi tramite Google", + "page.login.oidc_signin": "Accedi tramite OpenID Connect", "page.integrations.title": "Integrazioni", "page.integration.miniflux_api": "API di Miniflux", "page.integration.miniflux_api_endpoint": "Endpoint dell'API di Miniflux", @@ -1715,8 +1730,11 @@ var translations = map[string]string{ "page.settings.title": "設定", "page.settings.link_google_account": "Google アカウントと接続する", "page.settings.unlink_google_account": "Google アカウントと接続を解除する", + "page.settings.link_oidc_account": "OpenID Connect アカウントと接続する", + "page.settings.unlink_oidc_account": "OpenID Connect アカウントと接続を解除する", "page.login.title": "ログイン", "page.login.google_signin": "Google アカウントでログイン", + "page.login.oidc_signin": "OpenID Connect アカウントでログイン", "page.integrations.title": "関連付け", "page.integration.miniflux_api": "Miniflux API", "page.integration.miniflux_api_endpoint": "API Endpoint", @@ -2019,6 +2037,9 @@ var translations = map[string]string{ "page.settings.title": "Instellingen", "page.settings.link_google_account": "Koppel mijn Google-account", "page.settings.unlink_google_account": "Ontkoppel mijn Google-account", + "page.settings.link_oidc_account": "Koppel mijn OpenID Connect-account", + "page.settings.unlink_oidc_account": "Ontkoppel mijn OpenID Connect-account", + "page.login.oidc_signin": "Inloggen via OpenID Connect", "page.login.google_signin": "Inloggen via Google", "page.integrations.title": "Integraties", "page.integration.miniflux_api": "Miniflux API", @@ -2341,8 +2362,11 @@ var translations = map[string]string{ "page.settings.title": "Ustawienia", "page.settings.link_google_account": "Połącz z moim kontem Google", "page.settings.unlink_google_account": "Odłącz moje konto Google", + "page.settings.link_oidc_account": "Połącz z moim kontem OpenID Connect", + "page.settings.unlink_oidc_account": "Odłącz moje konto OpenID Connect", "page.login.title": "Zaloguj się", "page.login.google_signin": "Zaloguj przez Google", + "page.login.oidc_signin": "Zaloguj przez OpenID Connect", "page.integrations.title": "Usługi", "page.integration.miniflux_api": "Miniflux API", "page.integration.miniflux_api_endpoint": "Punkt końcowy API", @@ -2670,8 +2694,11 @@ var translations = map[string]string{ "page.settings.title": "Настройки", "page.settings.link_google_account": "Привязать мой Google аккаунт", "page.settings.unlink_google_account": "Отвязать мой Google аккаунт", + "page.settings.link_oidc_account": "Привязать мой OpenID Connect аккаунт", + "page.settings.unlink_oidc_account": "Отвязать мой OpenID Connect аккаунт", "page.login.title": "Войти", "page.login.google_signin": "Войти с помощью Google", + "page.login.oidc_signin": "Войти с помощью OpenID Connect", "page.integrations.title": "Интеграции", "page.integration.miniflux_api": "Miniflux API", "page.integration.miniflux_api_endpoint": "Конечная точка API", @@ -2977,8 +3004,11 @@ var translations = map[string]string{ "page.settings.title": "设置", "page.settings.link_google_account": "关联我的 Google 账户", "page.settings.unlink_google_account": "解除 Google 账号关联", + "page.settings.link_oidc_account": "关联我的 OpenID Connect 账户", + "page.settings.unlink_oidc_account": "解除 OpenID Connect 账号关联", "page.login.title": "登陆", "page.login.google_signin": "使用 Google 登陆", + "page.login.oidc_signin": "使用 OpenID Connect 登陆", "page.integrations.title": "集成", "page.integration.miniflux_api": "Miniflux API", "page.integration.miniflux_api_endpoint": "API Endpoint", @@ -3139,14 +3169,14 @@ var translations = map[string]string{ } var translationsChecksums = map[string]string{ - "de_DE": "75ccff01dcd27613e2d130c5b6abdb6bb2645029c93373c7b96d8754298002cd", - "en_US": "f6ac2959fbe86b273ca3cd95031741dbfc4db25e8b61d6b29b798a9faefae4c6", - "es_ES": "a3a494acf1864b2cc6573f9627e5bd2f07fa96a14a39619f310e87e66a4f2c01", - "fr_FR": "9162d348af1c6d30bb6f16bb85468d394a353e9def08cf77adc47404889e6e78", - "it_IT": "ad12b1282ed9b3d1a785f92af70c07f3d7aecf49e8a5d1f023742636b24a366b", - "ja_JP": "a9994611dc3b6a6dd763b6bd1c89bc7c5ec9985a04059f6c45342077d42a3e05", - "nl_NL": "54e9b6cd6758ee3e699028104f25704d6569e5ed8793ff17e817ad80f1ef7bd2", - "pl_PL": "6a95a4f7e8bce0d0d0e0f56d46e69b4577a44609d15511d9fa11c81cb981b5d7", - "ru_RU": "cb024cd742298206634be390a19b7371a797ab8484615a69af7d8fdbea9b58f8", - "zh_CN": "a5f32c5e4714bce8638f7fd19b6c3e54937d9ab00b08ab655076d7be35ef76bd", + "de_DE": "cc826a57cf4bf789df38db4f50626ad8c1c2b84ce34075c2c04de3d1f0dcd2d5", + "en_US": "f7e6db53cdbc2c0d959ac231dbacf0ef4d0ed81248944c4a4f8b83ef000f5349", + "es_ES": "cc727f62eef3a6cba51b65253d70a50161af35bf9c5366281b7984b2fc189961", + "fr_FR": "d3d1a4bf9aa8e4e24bae2f117507dcfc3cf00660a73b44a6c42356e8dbab8ae8", + "it_IT": "5ded991f2c70ec2268e6053bd84a77cf4136ebaea42013d3e79d594f38abb1b3", + "ja_JP": "110d7a7b1c888282b031de340e3318a62cdd62076b05a7fb49759f554c6dbe76", + "nl_NL": "a934ab4b1eff85580425a5859c31fcb227ae8926deba74df4e42b5d4feb67826", + "pl_PL": "6e80c36788723b9a7ff3f372e13a55c68d153727ec0abb56663cadbf6d6e1d9f", + "ru_RU": "d56f9e31f63731d23ce1ea2a8a4cb019f3ab282b23a1f494c47061daea523587", + "zh_CN": "4a5ca40790fceab88257f6742dc05294b79142bee8aad6fc87fbd479d1941292", } diff --git a/locale/translations/de_DE.json b/locale/translations/de_DE.json index b1278038..07543b6f 100644 --- a/locale/translations/de_DE.json +++ b/locale/translations/de_DE.json @@ -154,9 +154,12 @@ "page.users.is_admin": "Administrator", "page.settings.title": "Einstellungen", "page.settings.link_google_account": "Google Konto verknüpfen", - "page.settings.unlink_google_account": "Diese Kategorie existiert nicht für diesen Benutzer", + "page.settings.unlink_google_account": "Google Konto Verknüpfung entfernen", + "page.settings.link_oidc_account": "OpenID Connect Konto verknüpfen", + "page.settings.unlink_oidc_account": "OpenID Connect Konto Verknüpfung entfernen", "page.login.title": "Anmeldung", "page.login.google_signin": "Anmeldung mit Google", + "page.login.oidc_signin": "Anmeldung mit OpenID Connect", "page.integrations.title": "Dienste", "page.integration.miniflux_api": "Miniflux API", "page.integration.miniflux_api_endpoint": "API Endpunkt", diff --git a/locale/translations/en_US.json b/locale/translations/en_US.json index bf516453..c3dc3b34 100644 --- a/locale/translations/en_US.json +++ b/locale/translations/en_US.json @@ -155,8 +155,11 @@ "page.settings.title": "Settings", "page.settings.link_google_account": "Link my Google account", "page.settings.unlink_google_account": "Unlink my Google account", + "page.settings.link_oidc_account": "Link my OpenID Connect account", + "page.settings.unlink_oidc_account": "Unlink my OpenID Connect account", "page.login.title": "Sign In", "page.login.google_signin": "Sign in with Google", + "page.login.oidc_signin": "Sign in with OpenID Connect", "page.integrations.title": "Integrations", "page.integration.miniflux_api": "Miniflux API", "page.integration.miniflux_api_endpoint": "API Endpoint", diff --git a/locale/translations/es_ES.json b/locale/translations/es_ES.json index bc68e44e..d1545e5c 100644 --- a/locale/translations/es_ES.json +++ b/locale/translations/es_ES.json @@ -155,8 +155,11 @@ "page.settings.title": "Ajustes", "page.settings.link_google_account": "Vincular mi cuenta de Google", "page.settings.unlink_google_account": "Desvincular mi cuenta de Google", + "page.settings.link_oidc_account": "Vincular mi cuenta de OpenID Connect", + "page.settings.unlink_oidc_account": "Desvincular mi cuenta de OpenID Connect", "page.login.title": "Iniciar sesión", "page.login.google_signin": "Iniciar sesión con tu cuenta de Google", + "page.login.oidc_signin": "Iniciar sesión con tu cuenta de OpenID Connect", "page.integrations.title": "Integraciones", "page.integration.miniflux_api": "API de Miniflux", "page.integration.miniflux_api_endpoint": "Extremo de API", diff --git a/locale/translations/fr_FR.json b/locale/translations/fr_FR.json index 860e475c..f01c9e35 100644 --- a/locale/translations/fr_FR.json +++ b/locale/translations/fr_FR.json @@ -155,8 +155,11 @@ "page.settings.title": "Réglages", "page.settings.link_google_account": "Associer mon compte Google", "page.settings.unlink_google_account": "Dissocier mon compte Google", + "page.settings.link_oidc_account": "Associer mon compte OpenID Connect", + "page.settings.unlink_oidc_account": "Dissocier mon compte OpenID Connect", "page.login.title": "Connexion", "page.login.google_signin": "Se connecter avec Google", + "page.login.oidc_signin": "Se connecter avec OpenID Connect", "page.integrations.title": "Intégrations", "page.integration.miniflux_api": "API de Miniflux", "page.integration.miniflux_api_endpoint": "Point de terminaison de l'API", diff --git a/locale/translations/it_IT.json b/locale/translations/it_IT.json index 72ecf74e..0ba57c8c 100644 --- a/locale/translations/it_IT.json +++ b/locale/translations/it_IT.json @@ -155,8 +155,11 @@ "page.settings.title": "Impostazioni", "page.settings.link_google_account": "Collega il mio account Google", "page.settings.unlink_google_account": "Scollega il mio account Google", + "page.settings.link_oidc_account": "Collega il mio account OpenID Connect", + "page.settings.unlink_oidc_account": "Scollega il mio account OpenID Connect", "page.login.title": "Accedi", "page.login.google_signin": "Accedi tramite Google", + "page.login.oidc_signin": "Accedi tramite OpenID Connect", "page.integrations.title": "Integrazioni", "page.integration.miniflux_api": "API di Miniflux", "page.integration.miniflux_api_endpoint": "Endpoint dell'API di Miniflux", diff --git a/locale/translations/ja_JP.json b/locale/translations/ja_JP.json index 1f8f6a13..4834b188 100644 --- a/locale/translations/ja_JP.json +++ b/locale/translations/ja_JP.json @@ -155,8 +155,11 @@ "page.settings.title": "設定", "page.settings.link_google_account": "Google アカウントと接続する", "page.settings.unlink_google_account": "Google アカウントと接続を解除する", + "page.settings.link_oidc_account": "OpenID Connect アカウントと接続する", + "page.settings.unlink_oidc_account": "OpenID Connect アカウントと接続を解除する", "page.login.title": "ログイン", "page.login.google_signin": "Google アカウントでログイン", + "page.login.oidc_signin": "OpenID Connect アカウントでログイン", "page.integrations.title": "関連付け", "page.integration.miniflux_api": "Miniflux API", "page.integration.miniflux_api_endpoint": "API Endpoint", diff --git a/locale/translations/nl_NL.json b/locale/translations/nl_NL.json index d5c2ba50..76dc686b 100644 --- a/locale/translations/nl_NL.json +++ b/locale/translations/nl_NL.json @@ -156,6 +156,9 @@ "page.settings.title": "Instellingen", "page.settings.link_google_account": "Koppel mijn Google-account", "page.settings.unlink_google_account": "Ontkoppel mijn Google-account", + "page.settings.link_oidc_account": "Koppel mijn OpenID Connect-account", + "page.settings.unlink_oidc_account": "Ontkoppel mijn OpenID Connect-account", + "page.login.oidc_signin": "Inloggen via OpenID Connect", "page.login.google_signin": "Inloggen via Google", "page.integrations.title": "Integraties", "page.integration.miniflux_api": "Miniflux API", diff --git a/locale/translations/pl_PL.json b/locale/translations/pl_PL.json index 3a5ed6d1..7c842f5e 100644 --- a/locale/translations/pl_PL.json +++ b/locale/translations/pl_PL.json @@ -157,8 +157,11 @@ "page.settings.title": "Ustawienia", "page.settings.link_google_account": "Połącz z moim kontem Google", "page.settings.unlink_google_account": "Odłącz moje konto Google", + "page.settings.link_oidc_account": "Połącz z moim kontem OpenID Connect", + "page.settings.unlink_oidc_account": "Odłącz moje konto OpenID Connect", "page.login.title": "Zaloguj się", "page.login.google_signin": "Zaloguj przez Google", + "page.login.oidc_signin": "Zaloguj przez OpenID Connect", "page.integrations.title": "Usługi", "page.integration.miniflux_api": "Miniflux API", "page.integration.miniflux_api_endpoint": "Punkt końcowy API", diff --git a/locale/translations/ru_RU.json b/locale/translations/ru_RU.json index 67885eed..b415a193 100644 --- a/locale/translations/ru_RU.json +++ b/locale/translations/ru_RU.json @@ -157,8 +157,11 @@ "page.settings.title": "Настройки", "page.settings.link_google_account": "Привязать мой Google аккаунт", "page.settings.unlink_google_account": "Отвязать мой Google аккаунт", + "page.settings.link_oidc_account": "Привязать мой OpenID Connect аккаунт", + "page.settings.unlink_oidc_account": "Отвязать мой OpenID Connect аккаунт", "page.login.title": "Войти", "page.login.google_signin": "Войти с помощью Google", + "page.login.oidc_signin": "Войти с помощью OpenID Connect", "page.integrations.title": "Интеграции", "page.integration.miniflux_api": "Miniflux API", "page.integration.miniflux_api_endpoint": "Конечная точка API", diff --git a/locale/translations/zh_CN.json b/locale/translations/zh_CN.json index e96aef18..44675d1e 100644 --- a/locale/translations/zh_CN.json +++ b/locale/translations/zh_CN.json @@ -153,8 +153,11 @@ "page.settings.title": "设置", "page.settings.link_google_account": "关联我的 Google 账户", "page.settings.unlink_google_account": "解除 Google 账号关联", + "page.settings.link_oidc_account": "关联我的 OpenID Connect 账户", + "page.settings.unlink_oidc_account": "解除 OpenID Connect 账号关联", "page.login.title": "登陆", "page.login.google_signin": "使用 Google 登陆", + "page.login.oidc_signin": "使用 OpenID Connect 登陆", "page.integrations.title": "集成", "page.integration.miniflux_api": "Miniflux API", "page.integration.miniflux_api_endpoint": "API Endpoint", diff --git a/oauth2/google.go b/oauth2/google.go index 5d3ca49c..87173764 100644 --- a/oauth2/google.go +++ b/oauth2/google.go @@ -31,9 +31,8 @@ func (g googleProvider) GetRedirectURL(state string) string { return g.config().AuthCodeURL(state) } -func (g googleProvider) GetProfile(code string) (*Profile, error) { +func (g googleProvider) GetProfile(ctx context.Context, code string) (*Profile, error) { conf := g.config() - ctx := context.Background() token, err := conf.Exchange(ctx, code) if err != nil { return nil, err diff --git a/oauth2/manager.go b/oauth2/manager.go index f1ba7d97..ea076217 100644 --- a/oauth2/manager.go +++ b/oauth2/manager.go @@ -4,7 +4,11 @@ package oauth2 // import "miniflux.app/oauth2" -import "errors" +import ( + "context" + "errors" + "miniflux.app/logger" +) // Manager handles OAuth2 providers. type Manager struct { @@ -26,8 +30,17 @@ func (m *Manager) AddProvider(name string, provider Provider) { } // NewManager returns a new Manager. -func NewManager(clientID, clientSecret, redirectURL string) *Manager { +func NewManager(ctx context.Context, clientID, clientSecret, redirectURL, oidcDiscoveryEndpoint string) *Manager { m := &Manager{providers: make(map[string]Provider)} m.AddProvider("google", newGoogleProvider(clientID, clientSecret, redirectURL)) + + if oidcDiscoveryEndpoint != "" { + if genericOidcProvider, err := newOidcProvider(ctx, clientID, clientSecret, redirectURL, oidcDiscoveryEndpoint); err != nil { + logger.Error("[OAuth2] failed to initialize OIDC provider: %v", err) + } else { + m.AddProvider("oidc", genericOidcProvider) + } + } + return m } diff --git a/oauth2/oidc.go b/oauth2/oidc.go new file mode 100644 index 00000000..c1e664dd --- /dev/null +++ b/oauth2/oidc.go @@ -0,0 +1,61 @@ +// Copyright 2017 Frédéric Guillot. All rights reserved. +// Use of this source code is governed by the Apache 2.0 +// license that can be found in the LICENSE file. + +package oauth2 // import "miniflux.app/oauth2" + +import ( + "context" + "github.com/coreos/go-oidc" + "golang.org/x/oauth2" +) + +type oidcProvider struct { + clientID string + clientSecret string + redirectURL string + provider *oidc.Provider +} + +func (o oidcProvider) GetUserExtraKey() string { + return "oidc_id" // FIXME? add extra options key to allow multiple OIDC providers each with their own extra key? +} + +func (o oidcProvider) GetRedirectURL(state string) string { + return o.config().AuthCodeURL(state) +} + +func (o oidcProvider) GetProfile(ctx context.Context, code string) (*Profile, error) { + conf := o.config() + token, err := conf.Exchange(ctx, code) + if err != nil { + return nil, err + } + + userInfo, err := o.provider.UserInfo(ctx, oauth2.StaticTokenSource(token)) + if err != nil { + return nil, err + } + + profile := &Profile{Key: o.GetUserExtraKey(), ID: userInfo.Subject, Username: userInfo.Email} + return profile, nil +} + +func (o oidcProvider) config() *oauth2.Config { + return &oauth2.Config{ + RedirectURL: o.redirectURL, + ClientID: o.clientID, + ClientSecret: o.clientSecret, + Scopes: []string{"openid", "email"}, + Endpoint: o.provider.Endpoint(), + } +} + +func newOidcProvider(ctx context.Context, clientID, clientSecret, redirectURL, discoveryEndpoint string) (*oidcProvider, error) { + provider, err := oidc.NewProvider(ctx, discoveryEndpoint) + if err != nil { + return nil, err + } + + return &oidcProvider{clientID: clientID, clientSecret: clientSecret, redirectURL: redirectURL, provider: provider}, nil +} diff --git a/oauth2/provider.go b/oauth2/provider.go index 04f92149..4c2e6f02 100644 --- a/oauth2/provider.go +++ b/oauth2/provider.go @@ -3,10 +3,11 @@ // license that can be found in the LICENSE file. package oauth2 // import "miniflux.app/oauth2" +import "context" // Provider is an interface for OAuth2 providers. type Provider interface { GetUserExtraKey() string GetRedirectURL(state string) string - GetProfile(code string) (*Profile, error) + GetProfile(ctx context.Context, code string) (*Profile, error) } diff --git a/template/html/login.html b/template/html/login.html index bc8202fa..341b9ca2 100644 --- a/template/html/login.html +++ b/template/html/login.html @@ -23,6 +23,10 @@
{{ t "page.login.google_signin" }}
+ {{ else if hasOAuth2Provider "oidc" }} +
+ {{ t "page.login.oidc_signin" }} +
{{ end }}