Merge branch 'master' of https://github.com/bitly/oauth2_proxy into work
This commit is contained in:
commit
0a334b7cf4
|
|
@ -1,5 +1,10 @@
|
|||
google_auth_proxy
|
||||
oauth2_proxy
|
||||
vendor
|
||||
dist
|
||||
.godeps
|
||||
*.exe
|
||||
|
||||
|
||||
# Go.gitignore
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
|
|
@ -23,9 +28,5 @@ _cgo_export.*
|
|||
|
||||
_testmain.go
|
||||
|
||||
*.exe
|
||||
dist
|
||||
.godeps
|
||||
|
||||
# Editor swap/temp files
|
||||
.*.swp
|
||||
|
|
|
|||
10
.travis.yml
10
.travis.yml
|
|
@ -1,11 +1,11 @@
|
|||
language: go
|
||||
go:
|
||||
- 1.7.5
|
||||
- 1.8.1
|
||||
- 1.8.x
|
||||
- 1.9.x
|
||||
script:
|
||||
- curl -s https://raw.githubusercontent.com/pote/gpm/v1.4.0/bin/gpm > gpm
|
||||
- chmod +x gpm
|
||||
- ./gpm install
|
||||
- wget -O dep https://github.com/golang/dep/releases/download/v0.3.2/dep-linux-amd64
|
||||
- chmod +x dep
|
||||
- ./dep ensure
|
||||
- ./test.sh
|
||||
sudo: false
|
||||
notifications:
|
||||
|
|
|
|||
10
Godeps
10
Godeps
|
|
@ -1,10 +0,0 @@
|
|||
github.com/18F/hmacauth 1.0.1
|
||||
github.com/BurntSushi/toml d94612f9fc140360834f9742158c70b5c5b5535b
|
||||
github.com/bitly/go-simplejson da1a8928f709389522c8023062a3739f3b4af419
|
||||
github.com/mreiferson/go-options 77551d20752b54535462404ad9d877ebdb26e53d
|
||||
github.com/bmizerany/assert e17e99893cb6509f428e1728281c2ad60a6b31e3
|
||||
gopkg.in/fsnotify.v1 v1.2.0
|
||||
golang.org/x/oauth2 7fdf09982454086d5570c7db3e11f360194830ca
|
||||
golang.org/x/net/context 242b6b35177ec3909636b6cf6a47e8c2c6324b5d
|
||||
google.golang.org/api/admin/directory/v1 650535c7d6201e8304c92f38c922a9a3a36c6877
|
||||
cloud.google.com/go/compute/metadata v0.7.0
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||
|
||||
|
||||
[[projects]]
|
||||
name = "cloud.google.com/go"
|
||||
packages = ["compute/metadata"]
|
||||
revision = "2d3a6656c17a60b0815b7e06ab0be04eacb6e613"
|
||||
version = "v0.16.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/BurntSushi/toml"
|
||||
packages = ["."]
|
||||
revision = "b26d9c308763d68093482582cea63d69be07a0f0"
|
||||
version = "v0.3.0"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/bitly/go-simplejson"
|
||||
packages = ["."]
|
||||
revision = "aabad6e819789e569bd6aabf444c935aa9ba1e44"
|
||||
version = "v0.5.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "v2"
|
||||
name = "github.com/coreos/go-oidc"
|
||||
packages = ["."]
|
||||
revision = "77e7f2010a464ade7338597afe650dfcffbe2ca8"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/davecgh/go-spew"
|
||||
packages = ["spew"]
|
||||
revision = "346938d642f2ec3594ed81d874461961cd0faa76"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/golang/protobuf"
|
||||
packages = ["proto"]
|
||||
revision = "1e59b77b52bf8e4b449a57e6f79f21226d571845"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/mbland/hmacauth"
|
||||
packages = ["."]
|
||||
revision = "107c17adcc5eccc9935cd67d9bc2feaf5255d2cb"
|
||||
version = "1.0.2"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/mreiferson/go-options"
|
||||
packages = ["."]
|
||||
revision = "77551d20752b54535462404ad9d877ebdb26e53d"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/pmezard/go-difflib"
|
||||
packages = ["difflib"]
|
||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/pquerna/cachecontrol"
|
||||
packages = [".","cacheobject"]
|
||||
revision = "0dec1b30a0215bb68605dfc568e8855066c9202d"
|
||||
|
||||
[[projects]]
|
||||
name = "github.com/stretchr/testify"
|
||||
packages = ["assert"]
|
||||
revision = "69483b4bd14f5845b5a1e55bca19e954e827f1d0"
|
||||
version = "v1.1.4"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/crypto"
|
||||
packages = ["ed25519","ed25519/internal/edwards25519"]
|
||||
revision = "9f005a07e0d31d45e6656d241bb5c0f2efd4bc94"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/net"
|
||||
packages = ["context","context/ctxhttp"]
|
||||
revision = "9dfe39835686865bff950a07b394c12a98ddc811"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/oauth2"
|
||||
packages = [".","google","internal","jws","jwt"]
|
||||
revision = "9ff8ebcc8e241d46f52ecc5bff0e5a2f2dbef402"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "google.golang.org/api"
|
||||
packages = ["admin/directory/v1","gensupport","googleapi","googleapi/internal/uritemplates"]
|
||||
revision = "8791354e7ab150705ede13637a18c1fcc16b62e8"
|
||||
|
||||
[[projects]]
|
||||
name = "google.golang.org/appengine"
|
||||
packages = [".","internal","internal/app_identity","internal/base","internal/datastore","internal/log","internal/modules","internal/remote_api","internal/urlfetch","urlfetch"]
|
||||
revision = "150dc57a1b433e64154302bdc40b6bb8aefa313a"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/fsnotify.v1"
|
||||
packages = ["."]
|
||||
revision = "836bfd95fecc0f1511dd66bdbf2b5b61ab8b00b6"
|
||||
version = "v1.2.11"
|
||||
|
||||
[[projects]]
|
||||
name = "gopkg.in/square/go-jose.v2"
|
||||
packages = [".","cipher","json"]
|
||||
revision = "f8f38de21b4dcd69d0413faf231983f5fd6634b1"
|
||||
version = "v2.1.3"
|
||||
|
||||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "efab48a0e196c2a849bfbe9aa02d2ae28d281ce1bfe9f23720d648858eefc8e6"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
|
||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||
# for detailed Gopkg.toml documentation.
|
||||
#
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/18F/hmacauth"
|
||||
version = "~1.0.1"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/BurntSushi/toml"
|
||||
version = "~0.3.0"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/bitly/go-simplejson"
|
||||
version = "~0.5.0"
|
||||
|
||||
[[constraint]]
|
||||
branch = "v2"
|
||||
name = "github.com/coreos/go-oidc"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/mreiferson/go-options"
|
||||
|
||||
[[constraint]]
|
||||
name = "github.com/stretchr/testify"
|
||||
version = "~1.1.4"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/oauth2"
|
||||
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "google.golang.org/api"
|
||||
|
||||
[[constraint]]
|
||||
name = "gopkg.in/fsnotify.v1"
|
||||
version = "~1.2.0"
|
||||
32
README.md
32
README.md
|
|
@ -32,7 +32,6 @@ Valid providers are :
|
|||
* [GitHub](#github-auth-provider)
|
||||
* [GitLab](#gitlab-auth-provider)
|
||||
* [LinkedIn](#linkedin-auth-provider)
|
||||
* [MyUSA](#myusa-auth-provider)
|
||||
|
||||
The provider can be selected using the `provider` configuration value.
|
||||
|
||||
|
|
@ -99,7 +98,7 @@ The Azure AD auth provider uses `openid` as it default scope. It uses `https://g
|
|||
The GitHub auth provider supports two additional parameters to restrict authentication to Organization or Team level access. Restricting by org and team is normally accompanied with `--email-domain=*`
|
||||
|
||||
-github-org="": restrict logins to members of this organisation
|
||||
-github-team="": restrict logins to members of any of these teams, separated by a comma
|
||||
-github-team="": restrict logins to members of any of these teams (slug), separated by a comma
|
||||
|
||||
If you are using GitHub enterprise, make sure you set the following to the appropriate url:
|
||||
|
||||
|
|
@ -115,7 +114,7 @@ If you are using self-hosted GitLab, make sure you set the following to the appr
|
|||
|
||||
-login-url="<your gitlab url>/oauth/authorize"
|
||||
-redeem-url="<your gitlab url>/oauth/token"
|
||||
-validate-url="<your gitlab url>/api/v3/user"
|
||||
-validate-url="<your gitlab url>/api/v4/user"
|
||||
|
||||
|
||||
### LinkedIn Auth Provider
|
||||
|
|
@ -129,10 +128,6 @@ For LinkedIn, the registration steps are:
|
|||
3. Fill in the remaining required fields and Save.
|
||||
4. Take note of the **Consumer Key / API Key** and **Consumer Secret / Secret Key**
|
||||
|
||||
### MyUSA Auth Provider
|
||||
|
||||
The [MyUSA](https://alpha.my.usa.gov) authentication service ([GitHub](https://github.com/18F/myusa))
|
||||
|
||||
### Microsoft Azure AD Provider
|
||||
|
||||
1. [Add an application](https://azure.microsoft.com/en-us/documentation/articles/active-directory-integrating-applications/) to your Azure Active Directory tenant.
|
||||
|
|
@ -153,6 +148,24 @@ The `filter-groups` flag enables a simple filter that will elide any groups that
|
|||
|
||||
The `permit-groups` flag requires that a user belong to a group that contains the specified string (or one of the specified strings). The X-Forwarded-Group header is checked for a `strings.Contains` match for each item in the list.
|
||||
|
||||
The 'group-delimiter' flag reflects how multiple group names will be represented in X-Forwarded-Groups header. By default group names will be delimited with '|' symbol
|
||||
|
||||
### OpenID Connect Provider
|
||||
|
||||
OpenID Connect is a spec for OAUTH 2.0 + identity that is implemented by many major providers and several open source projects. This provider was originally built against CoreOS Dex and we will use it as an example.
|
||||
|
||||
1. Launch a Dex instance using the [getting started guide](https://github.com/coreos/dex/blob/master/Documentation/getting-started.md).
|
||||
2. Setup oauth2_proxy with the correct provider and using the default ports and callbacks.
|
||||
3. Login with the fixture use in the dex guide and run the oauth2_proxy with the following args:
|
||||
|
||||
-provider oidc
|
||||
-client-id oauth2_proxy
|
||||
-client-secret proxy
|
||||
-redirect-url http://127.0.0.1:4180/oauth2/callback
|
||||
-oidc-issuer-url http://127.0.0.1:5556
|
||||
-cookie-secure=false
|
||||
-email-domain example.com
|
||||
|
||||
## Email Authentication
|
||||
|
||||
To authorize by email domain use `--email-domain=yourcompany.com`. To authorize individual email addresses use `--authenticated-emails-file=/path/to/file` with one email per line. To authorize all email addresses use `--email-domain=*`.
|
||||
|
|
@ -178,7 +191,7 @@ Usage of oauth2_proxy:
|
|||
-client-id string: the OAuth Client ID: ie: "123456.apps.googleusercontent.com"
|
||||
-client-secret string: the OAuth Client Secret
|
||||
-config string: path to config file
|
||||
-cookie-domain string: an optional cookie domain to force cookies to (ie: .yourcompany.com)*
|
||||
-cookie-domain string: an optional cookie domain to force cookies to (ie: .yourcompany.com)
|
||||
-cookie-expire duration: expire timeframe for cookie (default 168h0m0s)
|
||||
-cookie-httponly: set HttpOnly cookie flag (default true)
|
||||
-cookie-name string: the name of the cookie that the oauth_proxy creates (default "_oauth2_proxy")
|
||||
|
|
@ -189,9 +202,10 @@ Usage of oauth2_proxy:
|
|||
-display-htpasswd-form: display username / password login form if an htpasswd file is provided (default true)
|
||||
-email-domain value: authenticate emails with the specified domain (may be given multiple times). Use * to authenticate any email
|
||||
-filter-groups string: only pass groups in the X-Forwarded-Groups header that contain this string
|
||||
-groups-delimiter string: delimiter that will be used between group names in X-Forwarded-Groups header
|
||||
-footer string: custom footer string. Use "-" to disable default footer.
|
||||
-github-org string: restrict logins to members of this organisation
|
||||
-github-team string: restrict logins to members of this team
|
||||
-github-team string: restrict logins to members of any of these teams (slug), separated by a comma
|
||||
-google-admin-email string: the google admin to impersonate for api calls
|
||||
-google-group value: restrict logins to members of this google group (may be given multiple times).
|
||||
-google-service-account-json string: the path to the service account json credentials
|
||||
|
|
|
|||
|
|
@ -2,12 +2,13 @@ package api
|
|||
|
||||
import (
|
||||
"github.com/bitly/go-simplejson"
|
||||
"github.com/bmizerany/assert"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testBackend(response_code int, payload string) *httptest.Server {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"encoding/base64"
|
||||
"testing"
|
||||
|
||||
"github.com/bmizerany/assert"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEncodeAndDecodeAccessToken(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/bmizerany/assert"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type envTest struct {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/bmizerany/assert"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
|
|||
2
main.go
2
main.go
|
|
@ -37,6 +37,7 @@ func main() {
|
|||
flagSet.Bool("pass-groups", false, "pass user group information in the X-Forwarded-Groups header to upstream (Azure only)")
|
||||
flagSet.String("filter-groups", "", "exclude groups that do not contain this value in its 'displayName' (Azure only)")
|
||||
flagSet.Var(&permittedGroups, "permit-groups", "restrict logins to members of this group (may be given multiple times; Azure and Google only).")
|
||||
flagSet.String("groups-delimiter", "|", "delimiter between group names if more than one found. By default it is '|' symbol")
|
||||
flagSet.String("basic-auth-password", "", "the password to set when passing the HTTP Basic Auth header")
|
||||
flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header")
|
||||
flagSet.Bool("pass-host-header", true, "pass the request Host Header to upstream")
|
||||
|
|
@ -71,6 +72,7 @@ func main() {
|
|||
flagSet.Bool("request-logging", true, "Log requests to stdout")
|
||||
|
||||
flagSet.String("provider", "google", "OAuth provider")
|
||||
flagSet.String("oidc-issuer-url", "", "OpenID Connect issuer URL (ie: https://accounts.google.com)")
|
||||
flagSet.String("login-url", "", "Authentication endpoint")
|
||||
flagSet.String("redeem-url", "", "Token redemption endpoint")
|
||||
flagSet.String("profile-url", "", "Profile access endpoint")
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/18F/hmacauth"
|
||||
"github.com/bitly/oauth2_proxy/cookie"
|
||||
"github.com/bitly/oauth2_proxy/providers"
|
||||
"github.com/mbland/hmacauth"
|
||||
)
|
||||
|
||||
const SignatureHeader = "GAP-Signature"
|
||||
|
|
@ -64,6 +64,7 @@ type OAuthProxy struct {
|
|||
SetXAuthRequest bool
|
||||
PassBasicAuth bool
|
||||
PassGroups bool
|
||||
GroupsDelimiter string
|
||||
FilterGroups string
|
||||
SkipProviderButton bool
|
||||
PassUserHeaders bool
|
||||
|
|
@ -161,16 +162,12 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
|||
redirectURL.Path = fmt.Sprintf("%s/callback", opts.ProxyPrefix)
|
||||
|
||||
log.Printf("OAuthProxy configured for %s Client ID: %s", opts.provider.Data().ProviderName, opts.ClientID)
|
||||
domain := opts.CookieDomain
|
||||
if domain == "" {
|
||||
domain = "<default>"
|
||||
}
|
||||
refresh := "disabled"
|
||||
if opts.CookieRefresh != time.Duration(0) {
|
||||
refresh = fmt.Sprintf("after %s", opts.CookieRefresh)
|
||||
}
|
||||
|
||||
log.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHttpOnly, opts.CookieExpire, domain, refresh)
|
||||
log.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHttpOnly, opts.CookieExpire, opts.CookieDomain, refresh)
|
||||
|
||||
var cipher *cookie.Cipher
|
||||
if opts.PassAccessToken || (opts.CookieRefresh != time.Duration(0)) {
|
||||
|
|
@ -211,6 +208,7 @@ func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
|
|||
PassBasicAuth: opts.PassBasicAuth,
|
||||
PassUserHeaders: opts.PassUserHeaders,
|
||||
PassGroups: opts.PassGroups,
|
||||
GroupsDelimiter: opts.GroupsDelimiter,
|
||||
FilterGroups: opts.FilterGroups,
|
||||
BasicAuthPassword: opts.BasicAuthPassword,
|
||||
PassAccessToken: opts.PassAccessToken,
|
||||
|
|
@ -257,6 +255,13 @@ func (p *OAuthProxy) redeemCode(host, code string) (s *providers.SessionState, e
|
|||
if s.Email == "" {
|
||||
s.Email, err = p.provider.GetEmailAddress(s)
|
||||
}
|
||||
|
||||
if s.User == "" {
|
||||
s.User, err = p.provider.GetUserName(s)
|
||||
if err != nil && err.Error() == "not implemented" {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -276,22 +281,21 @@ func (p *OAuthProxy) MakeCSRFCookie(req *http.Request, value string, expiration
|
|||
}
|
||||
|
||||
func (p *OAuthProxy) makeCookie(req *http.Request, name string, value string, expiration time.Duration, now time.Time) *http.Cookie {
|
||||
if p.CookieDomain != "" {
|
||||
domain := req.Host
|
||||
if h, _, err := net.SplitHostPort(domain); err == nil {
|
||||
domain = h
|
||||
}
|
||||
if p.CookieDomain != "" {
|
||||
if !strings.HasSuffix(domain, p.CookieDomain) {
|
||||
log.Printf("Warning: request host is %q but using configured cookie domain of %q", domain, p.CookieDomain)
|
||||
}
|
||||
domain = p.CookieDomain
|
||||
}
|
||||
|
||||
return &http.Cookie{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Path: "/",
|
||||
Domain: domain,
|
||||
Domain: p.CookieDomain,
|
||||
HttpOnly: p.CookieHttpOnly,
|
||||
Secure: p.CookieSecure,
|
||||
Expires: now.Add(expiration),
|
||||
|
|
@ -537,7 +541,6 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
|
|||
|
||||
errorString := req.Form.Get("error")
|
||||
if errorString != "" {
|
||||
log.Printf("[OAuthCallback] error in parsed form (REQ) : %s", req)
|
||||
log.Printf("[OAuthCallback] error in parsed form (REQ.Form) : %s", req.Form)
|
||||
log.Printf("[OAuthCallback] error in parsed form (REQ.error string) : %s", errorString)
|
||||
p.ErrorPage(rw, 403, "Permission Denied", errorString)
|
||||
|
|
@ -572,11 +575,12 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
|
|||
|
||||
session.IDToken = req.Form.Get("id_token")
|
||||
if p.PassGroups && session.IDToken != "" {
|
||||
session.Groups, err = p.provider.GetGroups(session, p.FilterGroups)
|
||||
groups, err := p.provider.GetGroups(session, p.FilterGroups)
|
||||
if err != nil {
|
||||
p.ErrorPage(rw, 500, "Internal Error", "Internal Error")
|
||||
return
|
||||
}
|
||||
session.Groups = strings.Join(groups, p.GroupsDelimiter)
|
||||
}
|
||||
|
||||
redirect = req.Form.Get("state")
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/18F/hmacauth"
|
||||
"github.com/bitly/oauth2_proxy/providers"
|
||||
"github.com/bmizerany/assert"
|
||||
"github.com/mbland/hmacauth"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
|
|||
49
options.go
49
options.go
|
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/tls"
|
||||
"encoding/base64"
|
||||
|
|
@ -12,8 +13,9 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/18F/hmacauth"
|
||||
"github.com/bitly/oauth2_proxy/providers"
|
||||
oidc "github.com/coreos/go-oidc"
|
||||
"github.com/mbland/hmacauth"
|
||||
)
|
||||
|
||||
// Configuration Options that can be set by Command Line Flag, or Config File
|
||||
|
|
@ -53,6 +55,7 @@ type Options struct {
|
|||
PassGroups bool `flag:"pass-groups" cfg:"pass_groups"`
|
||||
FilterGroups string `flag:"filter-groups" cfg:"filter_groups"`
|
||||
PermitGroups []string `flag:"permit-groups" cfg:"permit_groups"`
|
||||
GroupsDelimiter string `flag:"groups-delimiter" cfg:"groups_delimiter"`
|
||||
BasicAuthPassword string `flag:"basic-auth-password" cfg:"basic_auth_password"`
|
||||
PassAccessToken bool `flag:"pass-access-token" cfg:"pass_access_token"`
|
||||
PassHostHeader bool `flag:"pass-host-header" cfg:"pass_host_header"`
|
||||
|
|
@ -65,6 +68,7 @@ type Options struct {
|
|||
// These options allow for other providers besides Google, with
|
||||
// potential overrides.
|
||||
Provider string `flag:"provider" cfg:"provider"`
|
||||
OIDCIssuerURL string `flag:"oidc-issuer-url" cfg:"oidc_issuer_url"`
|
||||
LoginURL string `flag:"login-url" cfg:"login_url"`
|
||||
RedeemURL string `flag:"redeem-url" cfg:"redeem_url"`
|
||||
ProfileURL string `flag:"profile-url" cfg:"profile_url"`
|
||||
|
|
@ -83,6 +87,7 @@ type Options struct {
|
|||
CompiledRegex []*regexp.Regexp
|
||||
provider providers.Provider
|
||||
signatureData *SignatureData
|
||||
oidcVerifier *oidc.IDTokenVerifier
|
||||
}
|
||||
|
||||
type SignatureData struct {
|
||||
|
|
@ -107,6 +112,7 @@ func NewOptions() *Options {
|
|||
PassUserHeaders: true,
|
||||
PassGroups: false,
|
||||
FilterGroups: "",
|
||||
GroupsDelimiter: "|",
|
||||
PassAccessToken: false,
|
||||
PassHostHeader: true,
|
||||
ApprovalPrompt: "",
|
||||
|
|
@ -125,6 +131,14 @@ func parseURL(to_parse string, urltype string, msgs []string) (*url.URL, []strin
|
|||
}
|
||||
|
||||
func (o *Options) Validate() error {
|
||||
if o.SSLInsecureSkipVerify {
|
||||
// TODO: Accept a certificate bundle.
|
||||
insecureTransport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
http.DefaultClient = &http.Client{Transport: insecureTransport}
|
||||
}
|
||||
|
||||
msgs := make([]string, 0)
|
||||
if o.CookieSecret == "" {
|
||||
msgs = append(msgs, "missing setting: cookie-secret")
|
||||
|
|
@ -140,6 +154,22 @@ func (o *Options) Validate() error {
|
|||
"\n use email-domain=* to authorize all email addresses")
|
||||
}
|
||||
|
||||
if o.OIDCIssuerURL != "" {
|
||||
// Configure discoverable provider data.
|
||||
provider, err := oidc.NewProvider(context.Background(), o.OIDCIssuerURL)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.oidcVerifier = provider.Verifier(&oidc.Config{
|
||||
ClientID: o.ClientID,
|
||||
})
|
||||
o.LoginURL = provider.Endpoint().AuthURL
|
||||
o.RedeemURL = provider.Endpoint().TokenURL
|
||||
if o.Scope == "" {
|
||||
o.Scope = "openid email profile"
|
||||
}
|
||||
}
|
||||
|
||||
o.redirectURL, msgs = parseURL(o.RedirectURL, "redirect", msgs)
|
||||
|
||||
for _, u := range o.Upstreams {
|
||||
|
|
@ -157,8 +187,8 @@ func (o *Options) Validate() error {
|
|||
for _, u := range o.SkipAuthRegex {
|
||||
CompiledRegex, err := regexp.Compile(u)
|
||||
if err != nil {
|
||||
msgs = append(msgs, fmt.Sprintf(
|
||||
"error compiling regex=%q %s", u, err))
|
||||
msgs = append(msgs, fmt.Sprintf("error compiling regex=%q %s", u, err))
|
||||
continue
|
||||
}
|
||||
o.CompiledRegex = append(o.CompiledRegex, CompiledRegex)
|
||||
}
|
||||
|
|
@ -214,13 +244,6 @@ func (o *Options) Validate() error {
|
|||
msgs = parseSignatureKey(o, msgs)
|
||||
msgs = validateCookieName(o, msgs)
|
||||
|
||||
if o.SSLInsecureSkipVerify {
|
||||
insecureTransport := &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
}
|
||||
http.DefaultClient = &http.Client{Transport: insecureTransport}
|
||||
}
|
||||
|
||||
if len(msgs) != 0 {
|
||||
return fmt.Errorf("Invalid configuration:\n %s",
|
||||
strings.Join(msgs, "\n "))
|
||||
|
|
@ -263,6 +286,12 @@ func parseProviderInfo(o *Options, msgs []string) []string {
|
|||
p.SetGroupRestriction(o.PermitGroups, o.GoogleAdminEmail, file)
|
||||
}
|
||||
}
|
||||
case *providers.OIDCProvider:
|
||||
if o.oidcVerifier == nil {
|
||||
msgs = append(msgs, "oidc provider requires an oidc issuer URL")
|
||||
} else {
|
||||
p.Verifier = o.oidcVerifier
|
||||
}
|
||||
}
|
||||
return msgs
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/bmizerany/assert"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testOptions() *Options {
|
||||
|
|
@ -103,6 +103,18 @@ func TestProxyURLs(t *testing.T) {
|
|||
assert.Equal(t, expected, o.proxyURLs)
|
||||
}
|
||||
|
||||
func TestProxyURLsError(t *testing.T) {
|
||||
o := testOptions()
|
||||
o.Upstreams = append(o.Upstreams, "127.0.0.1:8081")
|
||||
err := o.Validate()
|
||||
assert.NotEqual(t, nil, err)
|
||||
|
||||
expected := errorMsg([]string{
|
||||
"error parsing upstream: parse 127.0.0.1:8081: " +
|
||||
"first path segment in URL cannot contain colon"})
|
||||
assert.Equal(t, expected, err.Error())
|
||||
}
|
||||
|
||||
func TestCompiledRegex(t *testing.T) {
|
||||
o := testOptions()
|
||||
regexps := []string{"/foo/.*", "/ba[rz]/quux"}
|
||||
|
|
@ -127,6 +139,15 @@ func TestCompiledRegexError(t *testing.T) {
|
|||
"error compiling regex=\"barquux)\" error parsing regexp: " +
|
||||
"unexpected ): `barquux)`"})
|
||||
assert.Equal(t, expected, err.Error())
|
||||
|
||||
o.SkipAuthRegex = []string{"foobaz", "barquux)"}
|
||||
err = o.Validate()
|
||||
assert.NotEqual(t, nil, err)
|
||||
|
||||
expected = errorMsg([]string{
|
||||
"error compiling regex=\"barquux)\" error parsing regexp: " +
|
||||
"unexpected ): `barquux)`"})
|
||||
assert.Equal(t, expected, err.Error())
|
||||
}
|
||||
|
||||
func TestDefaultProviderApiSettings(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -130,13 +130,13 @@ func (p *AzureProvider) GetEmailAddress(s *SessionState) (string, error) {
|
|||
}
|
||||
|
||||
// Get list of groups user belong to. Filter the desired names of groups (in case of huge group set)
|
||||
func (p *AzureProvider) GetGroups(s *SessionState, f string) (string, error) {
|
||||
func (p *AzureProvider) GetGroups(s *SessionState, f string) ([]string, error) {
|
||||
if s.AccessToken == "" {
|
||||
return "", errors.New("missing access token")
|
||||
return []string{}, errors.New("missing access token")
|
||||
}
|
||||
|
||||
if s.IDToken == "" {
|
||||
return "", errors.New("missing id token")
|
||||
return []string{}, errors.New("missing id token")
|
||||
}
|
||||
|
||||
// For future use. Right now microsoft graph don't support filter
|
||||
|
|
@ -164,7 +164,7 @@ func (p *AzureProvider) GetGroups(s *SessionState, f string) (string, error) {
|
|||
// err = errors.New("fake error")
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
return []string{}, err
|
||||
}
|
||||
req.Header = getAzureHeader(s.AccessToken)
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
|
|
@ -174,7 +174,7 @@ func (p *AzureProvider) GetGroups(s *SessionState, f string) (string, error) {
|
|||
// If workaround already tried, just fail the execution
|
||||
if workaround_set {
|
||||
log.Printf("[GetGroups] We tried hard, but still receive error: '%s'", err)
|
||||
return "", err
|
||||
return []string{}, err
|
||||
}
|
||||
|
||||
// It might be that it is a Graph bug, try to workaround it by accessing another URL
|
||||
|
|
@ -204,7 +204,7 @@ func (p *AzureProvider) GetGroups(s *SessionState, f string) (string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
return strings.Join(groups, "|"), nil
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func (p *AzureProvider) GetLoginURL(redirectURI, state string) string {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package providers
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/bmizerany/assert"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
|
|
@ -10,6 +9,8 @@ import (
|
|||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -273,7 +274,8 @@ func TestAzureProviderNoGroups(t *testing.T) {
|
|||
http.DefaultClient.Transport = nil
|
||||
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, "", groups)
|
||||
// assert.Equal(t, "", groups)
|
||||
assert.Equal(t, []string{}, groups)
|
||||
}
|
||||
|
||||
func TestAzureProviderWrongRequestGroups(t *testing.T) {
|
||||
|
|
@ -292,7 +294,7 @@ func TestAzureProviderWrongRequestGroups(t *testing.T) {
|
|||
http.DefaultClient.Transport = nil
|
||||
|
||||
assert.NotEqual(t, nil, err)
|
||||
assert.Equal(t, "", groups)
|
||||
assert.Equal(t, []string{}, groups)
|
||||
}
|
||||
|
||||
func TestAzureProviderMultiRequestsGroups(t *testing.T) {
|
||||
|
|
@ -311,7 +313,7 @@ func TestAzureProviderMultiRequestsGroups(t *testing.T) {
|
|||
http.DefaultClient.Transport = nil
|
||||
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, "test-group-1|test-group-2|test-group-3", groups)
|
||||
assert.Equal(t, []string{"test-group-1", "test-group-2", "test-group-3"}, groups)
|
||||
}
|
||||
|
||||
func TestAzureEmptyPermittedGroups(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -218,10 +218,10 @@ func (p *GitHubProvider) GetEmailAddress(s *SessionState) (string, error) {
|
|||
if resp.StatusCode != 200 {
|
||||
return "", fmt.Errorf("got %d from %q %s",
|
||||
resp.StatusCode, endpoint.String(), body)
|
||||
} else {
|
||||
log.Printf("got %d from %q %s", resp.StatusCode, endpoint.String(), body)
|
||||
}
|
||||
|
||||
log.Printf("got %d from %q %s", resp.StatusCode, endpoint.String(), body)
|
||||
|
||||
if err := json.Unmarshal(body, &emails); err != nil {
|
||||
return "", fmt.Errorf("%s unmarshaling %s", err, body)
|
||||
}
|
||||
|
|
@ -234,3 +234,46 @@ func (p *GitHubProvider) GetEmailAddress(s *SessionState) (string, error) {
|
|||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func (p *GitHubProvider) GetUserName(s *SessionState) (string, error) {
|
||||
var user struct {
|
||||
Login string `json:"login"`
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
||||
endpoint := &url.URL{
|
||||
Scheme: p.ValidateURL.Scheme,
|
||||
Host: p.ValidateURL.Host,
|
||||
Path: path.Join(p.ValidateURL.Path, "/user"),
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", endpoint.String(), nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("could not create new GET request: %v", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", fmt.Sprintf("token %s", s.AccessToken))
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
defer resp.Body.Close()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
return "", fmt.Errorf("got %d from %q %s",
|
||||
resp.StatusCode, endpoint.String(), body)
|
||||
}
|
||||
|
||||
log.Printf("got %d from %q %s", resp.StatusCode, endpoint.String(), body)
|
||||
|
||||
if err := json.Unmarshal(body, &user); err != nil {
|
||||
return "", fmt.Errorf("%s unmarshaling %s", err, body)
|
||||
}
|
||||
|
||||
return user.Login, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,16 +6,11 @@ import (
|
|||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/bmizerany/assert"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func updateURL(url *url.URL, hostname string) {
|
||||
url.Scheme = "http"
|
||||
url.Host = hostname
|
||||
}
|
||||
|
||||
func testMyUsaProvider(hostname string) *MyUsaProvider {
|
||||
p := NewMyUsaProvider(
|
||||
func testGitHubProvider(hostname string) *GitHubProvider {
|
||||
p := NewGitHubProvider(
|
||||
&ProviderData{
|
||||
ProviderName: "",
|
||||
LoginURL: &url.URL{},
|
||||
|
|
@ -32,14 +27,19 @@ func testMyUsaProvider(hostname string) *MyUsaProvider {
|
|||
return p
|
||||
}
|
||||
|
||||
func testMyUsaBackend(payload string) *httptest.Server {
|
||||
path := "/api/v1/profile"
|
||||
query := "access_token=imaginary_access_token"
|
||||
func testGitHubBackend(payload string) *httptest.Server {
|
||||
pathToQueryMap := map[string]string{
|
||||
"/user": "",
|
||||
"/user/emails": "",
|
||||
}
|
||||
|
||||
return httptest.NewServer(http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
url := r.URL
|
||||
if url.Path != path || url.RawQuery != query {
|
||||
query, ok := pathToQueryMap[url.Path]
|
||||
if !ok {
|
||||
w.WriteHeader(404)
|
||||
} else if url.RawQuery != query {
|
||||
w.WriteHeader(404)
|
||||
} else {
|
||||
w.WriteHeader(200)
|
||||
|
|
@ -48,60 +48,52 @@ func testMyUsaBackend(payload string) *httptest.Server {
|
|||
}))
|
||||
}
|
||||
|
||||
func TestMyUsaProviderDefaults(t *testing.T) {
|
||||
p := testMyUsaProvider("")
|
||||
func TestGitHubProviderDefaults(t *testing.T) {
|
||||
p := testGitHubProvider("")
|
||||
assert.NotEqual(t, nil, p)
|
||||
assert.Equal(t, "MyUSA", p.Data().ProviderName)
|
||||
assert.Equal(t, "https://alpha.my.usa.gov/oauth/authorize",
|
||||
assert.Equal(t, "GitHub", p.Data().ProviderName)
|
||||
assert.Equal(t, "https://github.com/login/oauth/authorize",
|
||||
p.Data().LoginURL.String())
|
||||
assert.Equal(t, "https://alpha.my.usa.gov/oauth/token",
|
||||
assert.Equal(t, "https://github.com/login/oauth/access_token",
|
||||
p.Data().RedeemURL.String())
|
||||
assert.Equal(t, "https://alpha.my.usa.gov/api/v1/profile",
|
||||
p.Data().ProfileURL.String())
|
||||
assert.Equal(t, "https://alpha.my.usa.gov/api/v1/tokeninfo",
|
||||
assert.Equal(t, "https://api.github.com/",
|
||||
p.Data().ValidateURL.String())
|
||||
assert.Equal(t, "profile.email", p.Data().Scope)
|
||||
assert.Equal(t, "user:email", p.Data().Scope)
|
||||
}
|
||||
|
||||
func TestMyUsaProviderOverrides(t *testing.T) {
|
||||
p := NewMyUsaProvider(
|
||||
func TestGitHubProviderOverrides(t *testing.T) {
|
||||
p := NewGitHubProvider(
|
||||
&ProviderData{
|
||||
LoginURL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "example.com",
|
||||
Path: "/oauth/auth"},
|
||||
Path: "/login/oauth/authorize"},
|
||||
RedeemURL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "example.com",
|
||||
Path: "/oauth/token"},
|
||||
ProfileURL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "example.com",
|
||||
Path: "/oauth/profile"},
|
||||
Path: "/login/oauth/access_token"},
|
||||
ValidateURL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "example.com",
|
||||
Path: "/oauth/tokeninfo"},
|
||||
Host: "api.example.com",
|
||||
Path: "/"},
|
||||
Scope: "profile"})
|
||||
assert.NotEqual(t, nil, p)
|
||||
assert.Equal(t, "MyUSA", p.Data().ProviderName)
|
||||
assert.Equal(t, "https://example.com/oauth/auth",
|
||||
assert.Equal(t, "GitHub", p.Data().ProviderName)
|
||||
assert.Equal(t, "https://example.com/login/oauth/authorize",
|
||||
p.Data().LoginURL.String())
|
||||
assert.Equal(t, "https://example.com/oauth/token",
|
||||
assert.Equal(t, "https://example.com/login/oauth/access_token",
|
||||
p.Data().RedeemURL.String())
|
||||
assert.Equal(t, "https://example.com/oauth/profile",
|
||||
p.Data().ProfileURL.String())
|
||||
assert.Equal(t, "https://example.com/oauth/tokeninfo",
|
||||
assert.Equal(t, "https://api.example.com/",
|
||||
p.Data().ValidateURL.String())
|
||||
assert.Equal(t, "profile", p.Data().Scope)
|
||||
}
|
||||
|
||||
func TestMyUsaProviderGetEmailAddress(t *testing.T) {
|
||||
b := testMyUsaBackend("{\"email\": \"michael.bland@gsa.gov\"}")
|
||||
func TestGitHubProviderGetEmailAddress(t *testing.T) {
|
||||
b := testGitHubBackend(`[ {"email": "michael.bland@gsa.gov", "primary": true} ]`)
|
||||
defer b.Close()
|
||||
|
||||
b_url, _ := url.Parse(b.URL)
|
||||
p := testMyUsaProvider(b_url.Host)
|
||||
bURL, _ := url.Parse(b.URL)
|
||||
p := testGitHubProvider(bURL.Host)
|
||||
|
||||
session := &SessionState{AccessToken: "imaginary_access_token"}
|
||||
email, err := p.GetEmailAddress(session)
|
||||
|
|
@ -111,12 +103,12 @@ func TestMyUsaProviderGetEmailAddress(t *testing.T) {
|
|||
|
||||
// Note that trying to trigger the "failed building request" case is not
|
||||
// practical, since the only way it can fail is if the URL fails to parse.
|
||||
func TestMyUsaProviderGetEmailAddressFailedRequest(t *testing.T) {
|
||||
b := testMyUsaBackend("unused payload")
|
||||
func TestGitHubProviderGetEmailAddressFailedRequest(t *testing.T) {
|
||||
b := testGitHubBackend("unused payload")
|
||||
defer b.Close()
|
||||
|
||||
b_url, _ := url.Parse(b.URL)
|
||||
p := testMyUsaProvider(b_url.Host)
|
||||
bURL, _ := url.Parse(b.URL)
|
||||
p := testGitHubProvider(bURL.Host)
|
||||
|
||||
// We'll trigger a request failure by using an unexpected access
|
||||
// token. Alternatively, we could allow the parsing of the payload as
|
||||
|
|
@ -127,15 +119,28 @@ func TestMyUsaProviderGetEmailAddressFailedRequest(t *testing.T) {
|
|||
assert.Equal(t, "", email)
|
||||
}
|
||||
|
||||
func TestMyUsaProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) {
|
||||
b := testMyUsaBackend("{\"foo\": \"bar\"}")
|
||||
func TestGitHubProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) {
|
||||
b := testGitHubBackend("{\"foo\": \"bar\"}")
|
||||
defer b.Close()
|
||||
|
||||
b_url, _ := url.Parse(b.URL)
|
||||
p := testMyUsaProvider(b_url.Host)
|
||||
bURL, _ := url.Parse(b.URL)
|
||||
p := testGitHubProvider(bURL.Host)
|
||||
|
||||
session := &SessionState{AccessToken: "imaginary_access_token"}
|
||||
email, err := p.GetEmailAddress(session)
|
||||
assert.NotEqual(t, nil, err)
|
||||
assert.Equal(t, "", email)
|
||||
}
|
||||
|
||||
func TestGitHubProviderGetUserName(t *testing.T) {
|
||||
b := testGitHubBackend(`{"email": "michael.bland@gsa.gov", "login": "mbland"}`)
|
||||
defer b.Close()
|
||||
|
||||
bURL, _ := url.Parse(b.URL)
|
||||
p := testGitHubProvider(bURL.Host)
|
||||
|
||||
session := &SessionState{AccessToken: "imaginary_access_token"}
|
||||
email, err := p.GetUserName(session)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, "mbland", email)
|
||||
}
|
||||
|
|
@ -32,11 +32,11 @@ func NewGitLabProvider(p *ProviderData) *GitLabProvider {
|
|||
p.ValidateURL = &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "gitlab.com",
|
||||
Path: "/api/v3/user",
|
||||
Path: "/api/v4/user",
|
||||
}
|
||||
}
|
||||
if p.Scope == "" {
|
||||
p.Scope = "api"
|
||||
p.Scope = "read_user"
|
||||
}
|
||||
return &GitLabProvider{ProviderData: p}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import (
|
|||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/bmizerany/assert"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testGitLabProvider(hostname string) *GitLabProvider {
|
||||
|
|
@ -28,7 +28,7 @@ func testGitLabProvider(hostname string) *GitLabProvider {
|
|||
}
|
||||
|
||||
func testGitLabBackend(payload string) *httptest.Server {
|
||||
path := "/api/v3/user"
|
||||
path := "/api/v4/user"
|
||||
query := "access_token=imaginary_access_token"
|
||||
|
||||
return httptest.NewServer(http.HandlerFunc(
|
||||
|
|
@ -51,9 +51,9 @@ func TestGitLabProviderDefaults(t *testing.T) {
|
|||
p.Data().LoginURL.String())
|
||||
assert.Equal(t, "https://gitlab.com/oauth/token",
|
||||
p.Data().RedeemURL.String())
|
||||
assert.Equal(t, "https://gitlab.com/api/v3/user",
|
||||
assert.Equal(t, "https://gitlab.com/api/v4/user",
|
||||
p.Data().ValidateURL.String())
|
||||
assert.Equal(t, "api", p.Data().Scope)
|
||||
assert.Equal(t, "read_user", p.Data().Scope)
|
||||
}
|
||||
|
||||
func TestGitLabProviderOverrides(t *testing.T) {
|
||||
|
|
@ -70,7 +70,7 @@ func TestGitLabProviderOverrides(t *testing.T) {
|
|||
ValidateURL: &url.URL{
|
||||
Scheme: "https",
|
||||
Host: "example.com",
|
||||
Path: "/api/v3/user"},
|
||||
Path: "/api/v4/user"},
|
||||
Scope: "profile"})
|
||||
assert.NotEqual(t, nil, p)
|
||||
assert.Equal(t, "GitLab", p.Data().ProviderName)
|
||||
|
|
@ -78,7 +78,7 @@ func TestGitLabProviderOverrides(t *testing.T) {
|
|||
p.Data().LoginURL.String())
|
||||
assert.Equal(t, "https://example.com/oauth/token",
|
||||
p.Data().RedeemURL.String())
|
||||
assert.Equal(t, "https://example.com/api/v3/user",
|
||||
assert.Equal(t, "https://example.com/api/v4/user",
|
||||
p.Data().ValidateURL.String())
|
||||
assert.Equal(t, "profile", p.Data().Scope)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ func emailFromIdToken(idToken string) (string, error) {
|
|||
// id_token is a base64 encode ID token payload
|
||||
// https://developers.google.com/accounts/docs/OAuth2Login#obtainuserinfo
|
||||
jwt := strings.Split(idToken, ".")
|
||||
b, err := jwtDecodeSegment(jwt[1])
|
||||
b, err := base64.RawURLEncoding.DecodeString(jwt[1])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
|
@ -89,14 +89,6 @@ func emailFromIdToken(idToken string) (string, error) {
|
|||
return email.Email, nil
|
||||
}
|
||||
|
||||
func jwtDecodeSegment(seg string) ([]byte, error) {
|
||||
if l := len(seg) % 4; l > 0 {
|
||||
seg += strings.Repeat("=", 4-l)
|
||||
}
|
||||
|
||||
return base64.URLEncoding.DecodeString(seg)
|
||||
}
|
||||
|
||||
func (p *GoogleProvider) Redeem(redirectURL, code string) (s *SessionState, err error) {
|
||||
if code == "" {
|
||||
err = errors.New("missing code")
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/bmizerany/assert"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func newRedeemServer(body []byte) (*url.URL, *httptest.Server) {
|
||||
|
|
|
|||
|
|
@ -72,3 +72,8 @@ func validateToken(p Provider, access_token string, header http.Header) bool {
|
|||
log.Printf("token validation request failed: status %d - %s", resp.StatusCode, body)
|
||||
return false
|
||||
}
|
||||
|
||||
func updateURL(url *url.URL, hostname string) {
|
||||
url.Scheme = "http"
|
||||
url.Host = hostname
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/bmizerany/assert"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type ValidateSessionStateTestProvider struct {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
package providers
|
||||
|
||||
import (
|
||||
"github.com/bmizerany/assert"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func testLinkedInProvider(hostname string) *LinkedInProvider {
|
||||
|
|
|
|||
|
|
@ -1,58 +0,0 @@
|
|||
package providers
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/bitly/oauth2_proxy/api"
|
||||
)
|
||||
|
||||
type MyUsaProvider struct {
|
||||
*ProviderData
|
||||
}
|
||||
|
||||
func NewMyUsaProvider(p *ProviderData) *MyUsaProvider {
|
||||
const myUsaHost string = "alpha.my.usa.gov"
|
||||
|
||||
p.ProviderName = "MyUSA"
|
||||
if p.LoginURL.String() == "" {
|
||||
p.LoginURL = &url.URL{Scheme: "https",
|
||||
Host: myUsaHost,
|
||||
Path: "/oauth/authorize"}
|
||||
}
|
||||
if p.RedeemURL.String() == "" {
|
||||
p.RedeemURL = &url.URL{Scheme: "https",
|
||||
Host: myUsaHost,
|
||||
Path: "/oauth/token"}
|
||||
}
|
||||
if p.ProfileURL.String() == "" {
|
||||
p.ProfileURL = &url.URL{Scheme: "https",
|
||||
Host: myUsaHost,
|
||||
Path: "/api/v1/profile"}
|
||||
}
|
||||
if p.ValidateURL.String() == "" {
|
||||
p.ValidateURL = &url.URL{Scheme: "https",
|
||||
Host: myUsaHost,
|
||||
Path: "/api/v1/tokeninfo"}
|
||||
}
|
||||
if p.Scope == "" {
|
||||
p.Scope = "profile.email"
|
||||
}
|
||||
return &MyUsaProvider{ProviderData: p}
|
||||
}
|
||||
|
||||
func (p *MyUsaProvider) GetEmailAddress(s *SessionState) (string, error) {
|
||||
req, err := http.NewRequest("GET",
|
||||
p.ProfileURL.String()+"?access_token="+s.AccessToken, nil)
|
||||
if err != nil {
|
||||
log.Printf("failed building request %s", err)
|
||||
return "", err
|
||||
}
|
||||
json, err := api.Request(req)
|
||||
if err != nil {
|
||||
log.Printf("failed making request %s", err)
|
||||
return "", err
|
||||
}
|
||||
return json.Get("email").String()
|
||||
}
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
package providers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"golang.org/x/oauth2"
|
||||
|
||||
oidc "github.com/coreos/go-oidc"
|
||||
)
|
||||
|
||||
type OIDCProvider struct {
|
||||
*ProviderData
|
||||
|
||||
Verifier *oidc.IDTokenVerifier
|
||||
}
|
||||
|
||||
func NewOIDCProvider(p *ProviderData) *OIDCProvider {
|
||||
p.ProviderName = "OpenID Connect"
|
||||
return &OIDCProvider{ProviderData: p}
|
||||
}
|
||||
|
||||
func (p *OIDCProvider) Redeem(redirectURL, code string) (s *SessionState, err error) {
|
||||
ctx := context.Background()
|
||||
c := oauth2.Config{
|
||||
ClientID: p.ClientID,
|
||||
ClientSecret: p.ClientSecret,
|
||||
Endpoint: oauth2.Endpoint{
|
||||
TokenURL: p.RedeemURL.String(),
|
||||
},
|
||||
RedirectURL: redirectURL,
|
||||
}
|
||||
token, err := c.Exchange(ctx, code)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("token exchange: %v", err)
|
||||
}
|
||||
|
||||
rawIDToken, ok := token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("token response did not contain an id_token")
|
||||
}
|
||||
|
||||
// Parse and verify ID Token payload.
|
||||
idToken, err := p.Verifier.Verify(ctx, rawIDToken)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not verify id_token: %v", err)
|
||||
}
|
||||
|
||||
// Extract custom claims.
|
||||
var claims struct {
|
||||
Email string `json:"email"`
|
||||
Verified *bool `json:"email_verified"`
|
||||
}
|
||||
if err := idToken.Claims(&claims); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse id_token claims: %v", err)
|
||||
}
|
||||
|
||||
if claims.Email == "" {
|
||||
return nil, fmt.Errorf("id_token did not contain an email")
|
||||
}
|
||||
if claims.Verified != nil && !*claims.Verified {
|
||||
return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email)
|
||||
}
|
||||
|
||||
s = &SessionState{
|
||||
AccessToken: token.AccessToken,
|
||||
RefreshToken: token.RefreshToken,
|
||||
ExpiresOn: token.Expiry,
|
||||
Email: claims.Email,
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (p *OIDCProvider) RefreshSessionIfNeeded(s *SessionState) (bool, error) {
|
||||
if s == nil || s.ExpiresOn.After(time.Now()) || s.RefreshToken == "" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
origExpiration := s.ExpiresOn
|
||||
s.ExpiresOn = time.Now().Add(time.Second).Truncate(time.Second)
|
||||
fmt.Printf("refreshed access token %s (expired on %s)\n", s, origExpiration)
|
||||
return false, nil
|
||||
}
|
||||
|
|
@ -111,10 +111,15 @@ func (p *ProviderData) GetEmailAddress(s *SessionState) (string, error) {
|
|||
return "", errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (p *ProviderData) GetGroups(s *SessionState, f string) (string, error) {
|
||||
// GetUserName returns the Account username
|
||||
func (p *ProviderData) GetUserName(s *SessionState) (string, error) {
|
||||
return "", errors.New("not implemented")
|
||||
}
|
||||
|
||||
func (p *ProviderData) GetGroups(s *SessionState, f string) ([]string, error) {
|
||||
return []string{}, errors.New("not implemented")
|
||||
}
|
||||
|
||||
// ValidateGroup validates that the provided email exists in the configured provider
|
||||
// email group(s).
|
||||
func (p *ProviderData) ValidateGroup(s *SessionState) bool {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/bmizerany/assert"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRefresh(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@ import (
|
|||
type Provider interface {
|
||||
Data() *ProviderData
|
||||
GetEmailAddress(*SessionState) (string, error)
|
||||
GetGroups(*SessionState, string) (string, error)
|
||||
GetUserName(*SessionState) (string, error)
|
||||
GetGroups(*SessionState, string) ([]string, error)
|
||||
Redeem(string, string) (*SessionState, error)
|
||||
ValidateGroup(*SessionState) bool
|
||||
ValidateSessionState(*SessionState) bool
|
||||
|
|
@ -19,8 +20,6 @@ type Provider interface {
|
|||
|
||||
func New(provider string, p *ProviderData) (Provider, error) {
|
||||
switch provider {
|
||||
case "myusa":
|
||||
return NewMyUsaProvider(p), nil
|
||||
case "linkedin":
|
||||
return NewLinkedInProvider(p), nil
|
||||
case "facebook":
|
||||
|
|
@ -31,6 +30,8 @@ func New(provider string, p *ProviderData) (Provider, error) {
|
|||
return NewAzureProvider(p), nil
|
||||
case "gitlab":
|
||||
return NewGitLabProvider(p), nil
|
||||
case "oidc":
|
||||
return NewOIDCProvider(p), nil
|
||||
default:
|
||||
return NewGoogleProvider(p), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package providers
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"github.com/bitly/oauth2_proxy/cookie"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bitly/oauth2_proxy/cookie"
|
||||
)
|
||||
|
||||
type SessionState struct {
|
||||
|
|
@ -27,7 +27,7 @@ func (s *SessionState) IsExpired() bool {
|
|||
}
|
||||
|
||||
func (s *SessionState) String() string {
|
||||
o := fmt.Sprintf("Session{%s", s.userOrEmail())
|
||||
o := fmt.Sprintf("Session{%s", s.accountInfo())
|
||||
if s.AccessToken != "" {
|
||||
o += " token:true"
|
||||
}
|
||||
|
|
@ -45,17 +45,13 @@ func (s *SessionState) String() string {
|
|||
|
||||
func (s *SessionState) EncodeSessionState(c *cookie.Cipher) (string, error) {
|
||||
if c == nil || s.AccessToken == "" {
|
||||
return s.userOrEmail(), nil
|
||||
return s.accountInfo(), nil
|
||||
}
|
||||
return s.EncryptedString(c)
|
||||
}
|
||||
|
||||
func (s *SessionState) userOrEmail() string {
|
||||
u := s.User
|
||||
if s.Email != "" {
|
||||
u = s.Email
|
||||
}
|
||||
return u
|
||||
func (s *SessionState) accountInfo() string {
|
||||
return fmt.Sprintf("email:%s user:%s", s.Email, s.User)
|
||||
}
|
||||
|
||||
func (s *SessionState) EncryptedString(c *cookie.Cipher) (string, error) {
|
||||
|
|
@ -65,59 +61,84 @@ func (s *SessionState) EncryptedString(c *cookie.Cipher) (string, error) {
|
|||
}
|
||||
a := s.AccessToken
|
||||
if a != "" {
|
||||
a, err = c.Encrypt(a)
|
||||
if err != nil {
|
||||
if a, err = c.Encrypt(a); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
r := s.RefreshToken
|
||||
if r != "" {
|
||||
r, err = c.Encrypt(r)
|
||||
if err != nil {
|
||||
if r, err = c.Encrypt(r); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s:%s:%d:%s:%s", s.userOrEmail(), a, s.ExpiresOn.Unix(), r, s.Groups), nil
|
||||
|
||||
encoded_groups := base64.StdEncoding.EncodeToString([]byte(s.Groups))
|
||||
|
||||
return fmt.Sprintf("%s|%s|%d|%s|%s", s.accountInfo(), a, s.ExpiresOn.Unix(), r, encoded_groups), nil
|
||||
}
|
||||
|
||||
func decodeSessionStatePlain(v string) (s *SessionState, err error) {
|
||||
chunks := strings.Split(v, " ")
|
||||
if len(chunks) != 2 {
|
||||
return nil, fmt.Errorf("could not decode session state: expected 2 chunks got %d", len(chunks))
|
||||
}
|
||||
|
||||
email := strings.TrimPrefix(chunks[0], "email:")
|
||||
user := strings.TrimPrefix(chunks[1], "user:")
|
||||
if user == "" {
|
||||
user = strings.Split(email, "@")[0]
|
||||
}
|
||||
|
||||
return &SessionState{User: user, Email: email}, nil
|
||||
}
|
||||
|
||||
func decodeUserGroups(v string) (groups string, err error) {
|
||||
decoded_groups, err := base64.StdEncoding.DecodeString(v)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(decoded_groups), nil
|
||||
}
|
||||
|
||||
func DecodeSessionState(v string, c *cookie.Cipher) (s *SessionState, err error) {
|
||||
chunks := strings.Split(v, ":")
|
||||
if len(chunks) == 1 {
|
||||
if strings.Contains(chunks[0], "@") {
|
||||
u := strings.Split(v, "@")[0]
|
||||
return &SessionState{Email: v, User: u}, nil
|
||||
}
|
||||
return &SessionState{User: v}, nil
|
||||
if c == nil {
|
||||
return decodeSessionStatePlain(v)
|
||||
}
|
||||
|
||||
chunks := strings.Split(v, "|")
|
||||
if len(chunks) != 5 {
|
||||
err = fmt.Errorf("invalid number of fields (got %d expected 5)", len(chunks))
|
||||
return
|
||||
}
|
||||
|
||||
s = &SessionState{}
|
||||
if c != nil && chunks[1] != "" {
|
||||
s.AccessToken, err = c.Decrypt(chunks[1])
|
||||
sessionState, err := decodeSessionStatePlain(chunks[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if c != nil && chunks[3] != "" {
|
||||
s.RefreshToken, err = c.Decrypt(chunks[3])
|
||||
if err != nil {
|
||||
|
||||
// Access Token
|
||||
if chunks[1] != "" {
|
||||
if sessionState.AccessToken, err = c.Decrypt(chunks[1]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if u := chunks[0]; strings.Contains(u, "@") {
|
||||
s.Email = u
|
||||
s.User = strings.Split(u, "@")[0]
|
||||
} else {
|
||||
s.User = u
|
||||
}
|
||||
if chunks[4] != "" {
|
||||
s.Groups = chunks[4]
|
||||
}
|
||||
|
||||
ts, _ := strconv.Atoi(chunks[2])
|
||||
s.ExpiresOn = time.Unix(int64(ts), 0)
|
||||
return
|
||||
sessionState.ExpiresOn = time.Unix(int64(ts), 0)
|
||||
|
||||
// Refresh Token
|
||||
if chunks[3] != "" {
|
||||
if sessionState.RefreshToken, err = c.Decrypt(chunks[3]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// User groups
|
||||
if chunks[4] != "" {
|
||||
if sessionState.Groups, err = decodeUserGroups(chunks[4]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return sessionState, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
package providers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/bitly/oauth2_proxy/cookie"
|
||||
"github.com/bmizerany/assert"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const secret = "0123456789abcdefghijklmnopqrstuv"
|
||||
|
|
@ -26,11 +28,15 @@ func TestSessionStateSerialization(t *testing.T) {
|
|||
}
|
||||
encoded, err := s.EncodeSessionState(c)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, 4, strings.Count(encoded, ":"))
|
||||
log.Printf("T = %v", t)
|
||||
log.Printf("encoded = %v", encoded)
|
||||
// assert.Equal(t, 4, strings.Count(encoded, ":"))
|
||||
assert.Equal(t, 4, strings.Count(encoded, "|"))
|
||||
|
||||
ss, err := DecodeSessionState(encoded, c)
|
||||
t.Logf("%#v", ss)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, "user", ss.User)
|
||||
assert.Equal(t, s.Email, ss.Email)
|
||||
assert.Equal(t, s.AccessToken, ss.AccessToken)
|
||||
assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
|
||||
|
|
@ -40,6 +46,44 @@ func TestSessionStateSerialization(t *testing.T) {
|
|||
ss, err = DecodeSessionState(encoded, c2)
|
||||
t.Logf("%#v", ss)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, "user", ss.User)
|
||||
assert.Equal(t, s.Email, ss.Email)
|
||||
assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
|
||||
assert.NotEqual(t, s.AccessToken, ss.AccessToken)
|
||||
assert.NotEqual(t, s.RefreshToken, ss.RefreshToken)
|
||||
}
|
||||
|
||||
func TestSessionStateSerializationWithUser(t *testing.T) {
|
||||
c, err := cookie.NewCipher([]byte(secret))
|
||||
assert.Equal(t, nil, err)
|
||||
c2, err := cookie.NewCipher([]byte(altSecret))
|
||||
assert.Equal(t, nil, err)
|
||||
s := &SessionState{
|
||||
User: "just-user",
|
||||
Email: "user@domain.com",
|
||||
AccessToken: "token1234",
|
||||
ExpiresOn: time.Now().Add(time.Duration(1) * time.Hour),
|
||||
RefreshToken: "refresh4321",
|
||||
}
|
||||
encoded, err := s.EncodeSessionState(c)
|
||||
assert.Equal(t, nil, err)
|
||||
log.Printf("Encoded string = %s", encoded)
|
||||
assert.Equal(t, 4, strings.Count(encoded, "|"))
|
||||
|
||||
ss, err := DecodeSessionState(encoded, c)
|
||||
t.Logf("%#v", ss)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, s.User, ss.User)
|
||||
assert.Equal(t, s.Email, ss.Email)
|
||||
assert.Equal(t, s.AccessToken, ss.AccessToken)
|
||||
assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
|
||||
assert.Equal(t, s.RefreshToken, ss.RefreshToken)
|
||||
|
||||
// ensure a different cipher can't decode properly (ie: it gets gibberish)
|
||||
ss, err = DecodeSessionState(encoded, c2)
|
||||
t.Logf("%#v", ss)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, s.User, ss.User)
|
||||
assert.Equal(t, s.Email, ss.Email)
|
||||
assert.Equal(t, s.ExpiresOn.Unix(), ss.ExpiresOn.Unix())
|
||||
assert.NotEqual(t, s.AccessToken, ss.AccessToken)
|
||||
|
|
@ -47,7 +91,6 @@ func TestSessionStateSerialization(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestSessionStateSerializationNoCipher(t *testing.T) {
|
||||
|
||||
s := &SessionState{
|
||||
Email: "user@domain.com",
|
||||
AccessToken: "token1234",
|
||||
|
|
@ -56,25 +99,51 @@ func TestSessionStateSerializationNoCipher(t *testing.T) {
|
|||
}
|
||||
encoded, err := s.EncodeSessionState(nil)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, s.Email, encoded)
|
||||
expected := fmt.Sprintf("email:%s user:", s.Email)
|
||||
assert.Equal(t, expected, encoded)
|
||||
|
||||
// only email should have been serialized
|
||||
ss, err := DecodeSessionState(encoded, nil)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, "user", ss.User)
|
||||
assert.Equal(t, s.Email, ss.Email)
|
||||
assert.Equal(t, "", ss.AccessToken)
|
||||
assert.Equal(t, "", ss.RefreshToken)
|
||||
}
|
||||
|
||||
func TestSessionStateUserOrEmail(t *testing.T) {
|
||||
func TestSessionStateSerializationNoCipherWithUser(t *testing.T) {
|
||||
s := &SessionState{
|
||||
User: "just-user",
|
||||
Email: "user@domain.com",
|
||||
AccessToken: "token1234",
|
||||
ExpiresOn: time.Now().Add(time.Duration(1) * time.Hour),
|
||||
RefreshToken: "refresh4321",
|
||||
}
|
||||
encoded, err := s.EncodeSessionState(nil)
|
||||
assert.Equal(t, nil, err)
|
||||
expected := fmt.Sprintf("email:%s user:%s", s.Email, s.User)
|
||||
assert.Equal(t, expected, encoded)
|
||||
|
||||
// only email should have been serialized
|
||||
ss, err := DecodeSessionState(encoded, nil)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Equal(t, s.User, ss.User)
|
||||
assert.Equal(t, s.Email, ss.Email)
|
||||
assert.Equal(t, "", ss.AccessToken)
|
||||
assert.Equal(t, "", ss.RefreshToken)
|
||||
}
|
||||
|
||||
func TestSessionStateAccountInfo(t *testing.T) {
|
||||
s := &SessionState{
|
||||
Email: "user@domain.com",
|
||||
User: "just-user",
|
||||
}
|
||||
assert.Equal(t, "user@domain.com", s.userOrEmail())
|
||||
expected := fmt.Sprintf("email:%v user:%v", s.Email, s.User)
|
||||
assert.Equal(t, expected, s.accountInfo())
|
||||
|
||||
s.Email = ""
|
||||
assert.Equal(t, "just-user", s.userOrEmail())
|
||||
expected = fmt.Sprintf("email:%v user:%v", s.Email, s.User)
|
||||
assert.Equal(t, expected, s.accountInfo())
|
||||
}
|
||||
|
||||
func TestExpired(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@ func getTemplates() *template.Template {
|
|||
{{ if .SignInMessage }}
|
||||
<p>{{.SignInMessage}}</p>
|
||||
{{ end}}
|
||||
<button type="submit" class="btn">Sign in with a {{.ProviderName}} Account</button><br/>
|
||||
<button type="submit" class="btn">Sign in with {{.ProviderName}}</button><br/>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/bmizerany/assert"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestTemplatesCompile(t *testing.T) {
|
||||
|
|
|
|||
21
test.sh
21
test.sh
|
|
@ -1,11 +1,14 @@
|
|||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
EXIT_CODE=0
|
||||
echo "gofmt"
|
||||
diff -u <(echo -n) <(gofmt -d $(find . -type f -name '*.go' -not -path "./.godeps/*"))
|
||||
echo "go vet"
|
||||
go vet ./...
|
||||
echo "go test"
|
||||
go test -timeout 60s ./...
|
||||
echo "go test -race"
|
||||
GOMAXPROCS=4 go test -timeout 60s -race ./...
|
||||
diff -u <(echo -n) <(gofmt -d $(find . -type f -name '*.go' -not -path "./vendor/*")) || EXIT_CODE=1
|
||||
for pkg in $(go list ./... | grep -v '/vendor/' ); do
|
||||
echo "testing $pkg"
|
||||
echo "go vet $pkg"
|
||||
go vet "$pkg" || EXIT_CODE=1
|
||||
echo "go test -v $pkg"
|
||||
go test -v -timeout 90s "$pkg" || EXIT_CODE=1
|
||||
echo "go test -v -race $pkg"
|
||||
GOMAXPROCS=4 go test -v -timeout 90s0s -race "$pkg" || EXIT_CODE=1
|
||||
done
|
||||
exit $EXIT_CODE
|
||||
|
|
@ -20,7 +20,7 @@ func NewValidatorTest(t *testing.T) *ValidatorTest {
|
|||
if err != nil {
|
||||
t.Fatal("failed to create temp file: " + err.Error())
|
||||
}
|
||||
vt.done = make(chan bool)
|
||||
vt.done = make(chan bool, 1)
|
||||
return vt
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ func TestValidatorOverwriteEmailListViaRenameAndReplace(t *testing.T) {
|
|||
|
||||
vt.WriteEmails(t, []string{"xyzzy@example.com"})
|
||||
domains := []string(nil)
|
||||
updated := make(chan bool)
|
||||
updated := make(chan bool, 1)
|
||||
validator := vt.NewValidator(domains, updated)
|
||||
|
||||
if !validator("xyzzy@example.com") {
|
||||
|
|
|
|||
Loading…
Reference in New Issue