aboutsummaryrefslogtreecommitdiff
path: root/server/auth
diff options
context:
space:
mode:
Diffstat (limited to 'server/auth')
-rw-r--r--server/auth/github/github.go70
-rw-r--r--server/auth/gitlab/gitlab.go225
-rw-r--r--server/auth/gitlab/gitlab_test.go98
-rw-r--r--server/auth/google/google.go135
-rw-r--r--server/auth/google/google_test.go75
-rw-r--r--server/auth/microsoft/microsoft.go201
-rw-r--r--server/auth/microsoft/microsoft_test.go72
-rw-r--r--server/auth/provider.go13
-rw-r--r--server/auth/testprovider/testprovider.go56
9 files changed, 36 insertions, 909 deletions
diff --git a/server/auth/github/github.go b/server/auth/github/github.go
index 38009e1..a46876d 100644
--- a/server/auth/github/github.go
+++ b/server/auth/github/github.go
@@ -6,7 +6,6 @@ import (
"net/http"
"time"
- "github.com/nsheridan/cashier/server/auth"
"github.com/nsheridan/cashier/server/config"
"github.com/nsheridan/cashier/server/metrics"
@@ -22,21 +21,23 @@ const (
// Config is an implementation of `auth.Provider` for authenticating using a
// Github account.
type Config struct {
- config *oauth2.Config
- organization string
- whitelist map[string]bool
+ config *oauth2.Config
+ orgWhitelist map[string]bool
+ userWhitelist map[string]bool
}
-var _ auth.Provider = (*Config)(nil)
-
// New creates a new Github provider from a configuration.
-func New(c *config.Auth) (*Config, error) {
+func New(c *config.Github) (*Config, error) {
uw := make(map[string]bool)
for _, u := range c.UsersWhitelist {
uw[u] = true
}
- if c.ProviderOpts["organization"] == "" && len(uw) == 0 {
- return nil, errors.New("either GitHub organization or users whitelist must be specified")
+ ow := make(map[string]bool)
+ for _, o := range c.OrgsWhitelist {
+ ow[o] = true
+ }
+ if len(uw) == 0 && len(ow) == 0 {
+ return nil, errors.New("either GitHub organizations or users whitelist must be specified")
}
return &Config{
config: &oauth2.Config{
@@ -49,8 +50,8 @@ func New(c *config.Auth) (*Config, error) {
string(githubapi.ScopeReadOrg),
},
},
- organization: c.ProviderOpts["organization"],
- whitelist: uw,
+ orgWhitelist: ow,
+ userWhitelist: uw,
}, nil
}
@@ -66,34 +67,35 @@ func (c *Config) Name() string {
// Valid validates the oauth token.
func (c *Config) Valid(token *oauth2.Token) bool {
- if len(c.whitelist) > 0 && !c.whitelist[c.Username(token)] {
- return false
- }
- if !token.Valid() {
- return false
- }
- if c.organization == "" {
- // There's no organization and the token is valid. Can only reach here
- // if there's a user whitelist set and the user is in the whitelist.
- metrics.M.AuthValid.WithLabelValues("github").Inc()
+ if c.isUserWhitelisted(token) {
return true
}
- client := githubapi.NewClient(c.newClient(token))
- member, _, err := client.Organizations.IsMember(context.TODO(), c.organization, c.Username(token))
- if err != nil {
- return false
- }
- if member {
- metrics.M.AuthValid.WithLabelValues("github").Inc()
+ if c.isMemberOfWhitelistedOrg(token) {
+ return true
}
- return member
+ return false
+}
+
+func (c *Config) isUserWhitelisted(token *oauth2.Token) bool {
+ username := c.Username(token)
+ _, ok := c.userWhitelist[username]
+ return ok
}
-// Revoke is a no-op revoke method. GitHub doesn't seem to allow token
-// revocation - tokens are indefinite and there are no refresh options etc.
-// Returns nil to satisfy the Provider interface.
-func (c *Config) Revoke(token *oauth2.Token) error {
- return nil
+func (c *Config) isMemberOfWhitelistedOrg(token *oauth2.Token) bool {
+ client := githubapi.NewClient(c.newClient(token))
+ username := c.Username(token)
+ for org, _ := range c.orgWhitelist {
+ member, _, err := client.Organizations.IsMember(context.TODO(), org, username)
+ if err != nil {
+ return false
+ }
+ if member {
+ metrics.M.AuthValid.WithLabelValues("github").Inc()
+ return true
+ }
+ }
+ return false
}
// StartSession retrieves an authentication endpoint from Github.
diff --git a/server/auth/gitlab/gitlab.go b/server/auth/gitlab/gitlab.go
deleted file mode 100644
index 70d3d1c..0000000
--- a/server/auth/gitlab/gitlab.go
+++ /dev/null
@@ -1,225 +0,0 @@
-package gitlab
-
-import (
- "bytes"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "log"
- "net/http"
- "strconv"
-
- "github.com/nsheridan/cashier/server/config"
- "github.com/nsheridan/cashier/server/metrics"
-
- "golang.org/x/oauth2"
-)
-
-const (
- name = "gitlab"
-)
-
-// Config is an implementation of `auth.Provider` for authenticating using a
-// Gitlab account.
-type Config struct {
- config *oauth2.Config
- group string
- whitelist map[string]bool
- allusers bool
- apiurl string
- log bool
-}
-
-// Note on Gitlab REST API calls. We don't parse errors because it's
-// kind of a pain:
-// https://gitlab.com/help/api/README.md#data-validation-and-error-reporting
-// The two v4 api calls used are /user and /groups/:group/members/:uid
-// https://gitlab.com/help/api/users.md#for-normal-users-1
-// https://gitlab.com/help/api/members.md#get-a-member-of-a-group-or-project
-type serviceUser struct {
- ID int `json:"id"`
- Username string `json:"username"`
- Email string `json:"email"`
-}
-
-type serviceGroupMember struct {
- ID int `json:"id"`
- State string `json:"state"`
- AccessLevel int `json:"access_level"`
-}
-
-func (c *Config) logMsg(message error) {
- if c.log {
- log.Print(message)
- }
-}
-
-// A new oauth2 http client.
-func (c *Config) newClient(token *oauth2.Token) *http.Client {
- return c.config.Client(oauth2.NoContext, token)
-}
-
-func (c *Config) getURL(token *oauth2.Token, url string) (*bytes.Buffer, error) {
- client := c.newClient(token)
- resp, err := client.Get(url)
- if err != nil {
- return nil, fmt.Errorf("Failed to get groups: %s", err)
- }
- defer resp.Body.Close()
- var body bytes.Buffer
- io.Copy(&body, resp.Body)
- if resp.StatusCode != 200 {
- return nil, fmt.Errorf("Gitlab error(http: %d) getting %s: '%s'",
- resp.StatusCode, url, body.String())
- }
- return &body, nil
-}
-
-// Gets info on the current user.
-func (c *Config) getUser(token *oauth2.Token) *serviceUser {
- url := c.apiurl + "user"
- body, err := c.getURL(token, url)
- if err != nil {
- c.logMsg(err)
- return nil
- }
- var user serviceUser
- if err := json.NewDecoder(body).Decode(&user); err != nil {
- c.logMsg(fmt.Errorf("Failed to decode user (%s): %s", url, err))
- return nil
- }
- return &user
-}
-
-// Gets current user group membership info.
-func (c *Config) checkGroupMembership(token *oauth2.Token, uid int, group string) bool {
- url := fmt.Sprintf("%sgroups/%s/members/%d", c.apiurl, group, uid)
- body, err := c.getURL(token, url)
- if err != nil {
- c.logMsg(err)
- return false
- }
- var m serviceGroupMember
- if err := json.NewDecoder(body).Decode(&m); err != nil {
- c.logMsg(fmt.Errorf("Failed to parse groups (%s): %s", url, err))
- return false
- }
- return m.ID == uid
-}
-
-// New creates a new Gitlab provider from a configuration.
-func New(c *config.Auth) (*Config, error) {
- logOpt, _ := strconv.ParseBool(c.ProviderOpts["log"])
- uw := make(map[string]bool)
- for _, u := range c.UsersWhitelist {
- uw[u] = true
- }
- allUsers, _ := strconv.ParseBool(c.ProviderOpts["allusers"])
- if !allUsers && c.ProviderOpts["group"] == "" && len(uw) == 0 {
- return nil, errors.New("gitlab_opts group and the users whitelist must not be both empty if allusers isn't true")
- }
- siteURL := "https://gitlab.com/"
- if c.ProviderOpts["siteurl"] != "" {
- siteURL = c.ProviderOpts["siteurl"]
- if siteURL[len(siteURL)-1] != '/' {
- return nil, errors.New("gitlab_opts siteurl must end in /")
- }
- } else {
- if allUsers {
- return nil, errors.New("gitlab_opts if allusers is set, siteurl must be set")
- }
- }
- // TODO: Should make sure siteURL is just the host bit.
- oauth2.RegisterBrokenAuthHeaderProvider(siteURL)
-
- return &Config{
- config: &oauth2.Config{
- ClientID: c.OauthClientID,
- ClientSecret: c.OauthClientSecret,
- RedirectURL: c.OauthCallbackURL,
- Endpoint: oauth2.Endpoint{
- AuthURL: siteURL + "oauth/authorize",
- TokenURL: siteURL + "oauth/token",
- },
- Scopes: []string{
- "api",
- },
- },
- group: c.ProviderOpts["group"],
- whitelist: uw,
- allusers: allUsers,
- apiurl: siteURL + "api/v4/",
- log: logOpt,
- }, nil
-}
-
-// Name returns the name of the provider.
-func (c *Config) Name() string {
- return name
-}
-
-// Valid validates the oauth token.
-func (c *Config) Valid(token *oauth2.Token) bool {
- if !token.Valid() {
- log.Printf("Auth fail (oauth2 Valid failure)")
- return false
- }
- if c.allusers {
- log.Printf("Auth success (allusers)")
- metrics.M.AuthValid.WithLabelValues("gitlab").Inc()
- return true
- }
- u := c.getUser(token)
- if u == nil {
- return false
- }
- if len(c.whitelist) > 0 && !c.whitelist[c.Username(token)] {
- c.logMsg(errors.New("Auth fail (not in whitelist)"))
- return false
- }
- if c.group == "" {
- // There's no group and token is valid. Can only reach
- // here if user whitelist is set and user is in whitelist.
- c.logMsg(errors.New("Auth success (no groups specified in server config)"))
- metrics.M.AuthValid.WithLabelValues("gitlab").Inc()
- return true
- }
- if !c.checkGroupMembership(token, u.ID, c.group) {
- c.logMsg(errors.New("Auth failure (not in allowed group)"))
- return false
- }
- metrics.M.AuthValid.WithLabelValues("gitlab").Inc()
- c.logMsg(errors.New("Auth success (in allowed group)"))
- return true
-}
-
-// Revoke is a no-op revoke method. Gitlab doesn't allow token
-// revocation - tokens live for an hour.
-// Returns nil to satisfy the Provider interface.
-func (c *Config) Revoke(token *oauth2.Token) error {
- return nil
-}
-
-// StartSession retrieves an authentication endpoint from Gitlab.
-func (c *Config) StartSession(state string) string {
- return c.config.AuthCodeURL(state)
-}
-
-// Exchange authorizes the session and returns an access token.
-func (c *Config) Exchange(code string) (*oauth2.Token, error) {
- t, err := c.config.Exchange(oauth2.NoContext, code)
- if err == nil {
- metrics.M.AuthExchange.WithLabelValues("gitlab").Inc()
- }
- return t, err
-}
-
-// Username retrieves the username of the Gitlab user.
-func (c *Config) Username(token *oauth2.Token) string {
- u := c.getUser(token)
- if u == nil {
- return ""
- }
- return u.Username
-}
diff --git a/server/auth/gitlab/gitlab_test.go b/server/auth/gitlab/gitlab_test.go
deleted file mode 100644
index 93b348b..0000000
--- a/server/auth/gitlab/gitlab_test.go
+++ /dev/null
@@ -1,98 +0,0 @@
-package gitlab
-
-import (
- "fmt"
- "testing"
-
- "github.com/nsheridan/cashier/server/auth"
- "github.com/nsheridan/cashier/server/config"
- "github.com/stretchr/testify/assert"
-)
-
-var (
- oauthClientID = "id"
- oauthClientSecret = "secret"
- oauthCallbackURL = "url"
- allusers = ""
- siteurl = "https://exampleorg/"
- group = "exampleorg"
-)
-
-func TestNew(t *testing.T) {
- a := assert.New(t)
-
- p, _ := newGitlab()
- g := p.(*Config)
- a.Equal(g.config.ClientID, oauthClientID)
- a.Equal(g.config.ClientSecret, oauthClientSecret)
- a.Equal(g.config.RedirectURL, oauthCallbackURL)
-}
-
-func TestNewBrokenSiteURL(t *testing.T) {
- siteurl = "https://exampleorg"
- a := assert.New(t)
-
- _, err := newGitlab()
- a.EqualError(err, "gitlab_opts siteurl must end in /")
-
- siteurl = "https://exampleorg/"
-}
-
-func TestBadAllUsers(t *testing.T) {
- allusers = "true"
- siteurl = ""
- a := assert.New(t)
-
- _, err := newGitlab()
- a.EqualError(err, "gitlab_opts if allusers is set, siteurl must be set")
-
- allusers = ""
- siteurl = "https://exampleorg/"
-}
-
-func TestGoodAllUsers(t *testing.T) {
- allusers = "true"
- a := assert.New(t)
-
- p, _ := newGitlab()
- s := p.StartSession("test_state")
- a.Contains(s, "exampleorg/oauth/authorize")
- a.Contains(s, "state=test_state")
- a.Contains(s, fmt.Sprintf("client_id=%s", oauthClientID))
-
- allusers = ""
-}
-
-func TestNewEmptyGroupList(t *testing.T) {
- group = ""
- a := assert.New(t)
-
- _, err := newGitlab()
- a.EqualError(err, "gitlab_opts group and the users whitelist must not be both empty if allusers isn't true")
-
- group = "exampleorg"
-}
-
-func TestStartSession(t *testing.T) {
- a := assert.New(t)
-
- p, _ := newGitlab()
- s := p.StartSession("test_state")
- a.Contains(s, "exampleorg/oauth/authorize")
- a.Contains(s, "state=test_state")
- a.Contains(s, fmt.Sprintf("client_id=%s", oauthClientID))
-}
-
-func newGitlab() (auth.Provider, error) {
- c := &config.Auth{
- OauthClientID: oauthClientID,
- OauthClientSecret: oauthClientSecret,
- OauthCallbackURL: oauthCallbackURL,
- ProviderOpts: map[string]string{
- "group": group,
- "siteurl": siteurl,
- "allusers": allusers,
- },
- }
- return New(c)
-}
diff --git a/server/auth/google/google.go b/server/auth/google/google.go
deleted file mode 100644
index b707310..0000000
--- a/server/auth/google/google.go
+++ /dev/null
@@ -1,135 +0,0 @@
-package google
-
-import (
- "errors"
- "fmt"
- "net/http"
- "strings"
-
- "github.com/nsheridan/cashier/server/auth"
- "github.com/nsheridan/cashier/server/config"
- "github.com/nsheridan/cashier/server/metrics"
-
- "golang.org/x/oauth2"
- "golang.org/x/oauth2/google"
- googleapi "google.golang.org/api/oauth2/v2"
-)
-
-const (
- revokeURL = "https://accounts.google.com/o/oauth2/revoke?token=%s"
- name = "google"
-)
-
-// Config is an implementation of `auth.Provider` for authenticating using a
-// Google account.
-type Config struct {
- config *oauth2.Config
- domain string
- whitelist map[string]bool
-}
-
-var _ auth.Provider = (*Config)(nil)
-
-// New creates a new Google provider from a configuration.
-func New(c *config.Auth) (*Config, error) {
- uw := make(map[string]bool)
- for _, u := range c.UsersWhitelist {
- uw[u] = true
- }
- if c.ProviderOpts["domain"] == "" && len(uw) == 0 {
- return nil, errors.New("either Google Apps domain or users whitelist must be specified")
- }
-
- return &Config{
- config: &oauth2.Config{
- ClientID: c.OauthClientID,
- ClientSecret: c.OauthClientSecret,
- RedirectURL: c.OauthCallbackURL,
- Endpoint: google.Endpoint,
- Scopes: []string{googleapi.UserinfoEmailScope, googleapi.UserinfoProfileScope},
- },
- domain: c.ProviderOpts["domain"],
- whitelist: uw,
- }, nil
-}
-
-// A new oauth2 http client.
-func (c *Config) newClient(token *oauth2.Token) *http.Client {
- return c.config.Client(oauth2.NoContext, token)
-}
-
-// Name returns the name of the provider.
-func (c *Config) Name() string {
- return name
-}
-
-// Valid validates the oauth token.
-func (c *Config) Valid(token *oauth2.Token) bool {
- if len(c.whitelist) > 0 && !c.whitelist[c.Email(token)] {
- return false
- }
- if !token.Valid() {
- return false
- }
- svc, err := googleapi.New(c.newClient(token))
- if err != nil {
- return false
- }
- t := svc.Tokeninfo()
- t.AccessToken(token.AccessToken)
- ti, err := t.Do()
- if err != nil {
- return false
- }
- if ti.Audience != c.config.ClientID {
- return false
- }
- ui, err := svc.Userinfo.Get().Do()
- if err != nil {
- return false
- }
- if c.domain != "" && ui.Hd != c.domain {
- return false
- }
- metrics.M.AuthValid.WithLabelValues("google").Inc()
- return true
-}
-
-// Revoke disables the access token.
-func (c *Config) Revoke(token *oauth2.Token) error {
- h := c.newClient(token)
- _, err := h.Get(fmt.Sprintf(revokeURL, token.AccessToken))
- return err
-}
-
-// StartSession retrieves an authentication endpoint from Google.
-func (c *Config) StartSession(state string) string {
- return c.config.AuthCodeURL(state, oauth2.SetAuthURLParam("hd", c.domain))
-}
-
-// Exchange authorizes the session and returns an access token.
-func (c *Config) Exchange(code string) (*oauth2.Token, error) {
- t, err := c.config.Exchange(oauth2.NoContext, code)
- if err == nil {
- metrics.M.AuthExchange.WithLabelValues("google").Inc()
- }
- return t, err
-}
-
-// Email retrieves the email address of the user.
-func (c *Config) Email(token *oauth2.Token) string {
- svc, err := googleapi.New(c.newClient(token))
- if err != nil {
- return ""
- }
- ui, err := svc.Userinfo.Get().Do()
- if err != nil {
- return ""
- }
- return ui.Email
-}
-
-// Username retrieves the username portion of the user's email address.
-func (c *Config) Username(token *oauth2.Token) string {
- return strings.Split(c.Email(token), "@")[0]
-}
diff --git a/server/auth/google/google_test.go b/server/auth/google/google_test.go
deleted file mode 100644
index 92e4ca0..0000000
--- a/server/auth/google/google_test.go
+++ /dev/null
@@ -1,75 +0,0 @@
-package google
-
-import (
- "fmt"
- "testing"
-
- "github.com/nsheridan/cashier/server/config"
- "github.com/stretchr/testify/assert"
-)
-
-var (
- oauthClientID = "id"
- oauthClientSecret = "secret"
- oauthCallbackURL = "url"
- domain = "example.com"
- users = []string{"user"}
-)
-
-func TestNew(t *testing.T) {
- a := assert.New(t)
- p, err := newGoogle()
- a.NoError(err)
- a.Equal(p.config.ClientID, oauthClientID)
- a.Equal(p.config.ClientSecret, oauthClientSecret)
- a.Equal(p.config.RedirectURL, oauthCallbackURL)
- a.Equal(p.domain, domain)
- a.Equal(p.whitelist, map[string]bool{"user": true})
-}
-
-func TestWhitelist(t *testing.T) {
- c := &config.Auth{
- OauthClientID: oauthClientID,
- OauthClientSecret: oauthClientSecret,
- OauthCallbackURL: oauthCallbackURL,
- ProviderOpts: map[string]string{"domain": ""},
- UsersWhitelist: []string{},
- }
- if _, err := New(c); err == nil {
- t.Error("creating a provider without a domain set should return an error")
- }
- // Set a user whitelist but no domain
- c.UsersWhitelist = users
- if _, err := New(c); err != nil {
- t.Error("creating a provider with users but no domain should not return an error")
- }
- // Unset the user whitelist and set a domain
- c.UsersWhitelist = []string{}
- c.ProviderOpts = map[string]string{"domain": domain}
- if _, err := New(c); err != nil {
- t.Error("creating a provider with a domain set but without a user whitelist should not return an error")
- }
-}
-
-func TestStartSession(t *testing.T) {
- a := assert.New(t)
-
- p, err := newGoogle()
- a.NoError(err)
- s := p.StartSession("test_state")
- a.Contains(s, "accounts.google.com/o/oauth2/auth")
- a.Contains(s, "state=test_state")
- a.Contains(s, fmt.Sprintf("hd=%s", domain))
- a.Contains(s, fmt.Sprintf("client_id=%s", oauthClientID))
-}
-
-func newGoogle() (*Config, error) {
- c := &config.Auth{
- OauthClientID: oauthClientID,
- OauthClientSecret: oauthClientSecret,
- OauthCallbackURL: oauthCallbackURL,
- ProviderOpts: map[string]string{"domain": domain},
- UsersWhitelist: users,
- }
- return New(c)
-}
diff --git a/server/auth/microsoft/microsoft.go b/server/auth/microsoft/microsoft.go
deleted file mode 100644
index 8463ccf..0000000
--- a/server/auth/microsoft/microsoft.go
+++ /dev/null
@@ -1,201 +0,0 @@
-package microsoft
-
-import (
- "encoding/json"
- "errors"
- "net/http"
- "path"
- "strings"
-
- "github.com/nsheridan/cashier/server/auth"
- "github.com/nsheridan/cashier/server/config"
- "github.com/nsheridan/cashier/server/metrics"
-
- "golang.org/x/oauth2"
- "golang.org/x/oauth2/microsoft"
-)
-
-const (
- name = "microsoft"
-)
-
-// Config is an implementation of `auth.Provider` for authenticating using a
-// Office 365 account.
-type Config struct {
- config *oauth2.Config
- tenant string
- groups map[string]bool
- whitelist map[string]bool
-}
-
-var _ auth.Provider = (*Config)(nil)
-
-// New creates a new Microsoft provider from a configuration.
-func New(c *config.Auth) (*Config, error) {
- whitelist := make(map[string]bool)
- for _, u := range c.UsersWhitelist {
- whitelist[u] = true
- }
- if c.ProviderOpts["tenant"] == "" && len(whitelist) == 0 {
- return nil, errors.New("either Office 365 tenant or users whitelist must be specified")
- }
- groupMap := make(map[string]bool)
- if groups, ok := c.ProviderOpts["groups"]; ok {
- for _, group := range strings.Split(groups, ",") {
- groupMap[strings.Trim(group, " ")] = true
- }
- }
-
- return &Config{
- config: &oauth2.Config{
- ClientID: c.OauthClientID,
- ClientSecret: c.OauthClientSecret,
- RedirectURL: c.OauthCallbackURL,
- Endpoint: microsoft.AzureADEndpoint(c.ProviderOpts["tenant"]),
- Scopes: []string{"user.Read.All", "Directory.Read.All"},
- },
- tenant: c.ProviderOpts["tenant"],
- whitelist: whitelist,
- groups: groupMap,
- }, nil
-}
-
-// A new oauth2 http client.
-func (c *Config) newClient(token *oauth2.Token) *http.Client {
- return c.config.Client(oauth2.NoContext, token)
-}
-
-// Gets a response for an graph api call.
-func (c *Config) getDocument(token *oauth2.Token, pathElements ...string) map[string]interface{} {
- client := c.newClient(token)
- url := "https://" + path.Join("graph.microsoft.com/v1.0", path.Join(pathElements...))
- resp, err := client.Get(url)
- if err != nil {
- return nil
- }
- defer resp.Body.Close()
- var document map[string]interface{}
- if err := json.NewDecoder(resp.Body).Decode(&document); err != nil {
- return nil
- }
- return document
-}
-
-// Get info from the "/me" endpoint of the Microsoft Graph API (MSG-API).
-// https://developer.microsoft.com/en-us/graph/docs/concepts/v1-overview
-func (c *Config) getMe(token *oauth2.Token, item string) string {
- document := c.getDocument(token, "/me")
- if value, ok := document[item].(string); ok {
- return value
- }
- return ""
-}
-
-// Check against verified domains from "/organization" endpoint of MSG-API.
-func (c *Config) verifyTenant(token *oauth2.Token) bool {
- document := c.getDocument(token, "/organization")
- // The domains for an organisation are in an array of structs under
- // verifiedDomains, which is in a struct which is in turn an array
- // of such structs under value in the document. Which in json looks
- // like this:
- // { "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#organization",
- // "value": [ {
- // ...
- // "verifiedDomains": [ {
- // ...
- // "name": "M365x214355.onmicrosoft.com",
- // } ]
- // } ]
- //}
- var value []interface{}
- var ok bool
- if value, ok = document["value"].([]interface{}); !ok {
- return false
- }
- for _, valueEntry := range value {
- if value, ok = valueEntry.(map[string]interface{})["verifiedDomains"].([]interface{}); !ok {
- continue
- }
- for _, val := range value {
- domain := val.(map[string]interface{})["name"].(string)
- if domain == c.tenant {
- return true
- }
- }
- }
- return false
-}
-
-// Check against groups from /users/{id}/memberOf endpoint of MSG-API.
-func (c *Config) verifyGroups(token *oauth2.Token) bool {
- document := c.getDocument(token, "/users/me/memberOf")
- var value []interface{}
- var ok bool
- if value, ok = document["value"].([]interface{}); !ok {
- return false
- }
- for _, valueEntry := range value {
- if group, ok := valueEntry.(map[string]interface{})["displayName"].(string); ok {
- if c.groups[group] {
- return true
- }
- }
- }
- return false
-}
-
-// Name returns the name of the provider.
-func (c *Config) Name() string {
- return name
-}
-
-// Valid validates the oauth token.
-func (c *Config) Valid(token *oauth2.Token) bool {
- if len(c.whitelist) > 0 && !c.whitelist[c.Email(token)] {
- return false
- }
- if !token.Valid() {
- return false
- }
- metrics.M.AuthValid.WithLabelValues("microsoft").Inc()
- if c.tenant != "" {
- if c.verifyTenant(token) {
- if len(c.groups) > 0 {
- return c.verifyGroups(token)
- }
- return true
- }
- }
- return false
-}
-
-// Revoke disables the access token.
-func (c *Config) Revoke(token *oauth2.Token) error {
- return nil
-}
-
-// StartSession retrieves an authentication endpoint from Microsoft.
-func (c *Config) StartSession(state string) string {
- return c.config.AuthCodeURL(state,
- oauth2.SetAuthURLParam("hd", c.tenant),
- oauth2.SetAuthURLParam("prompt", "login"))
-}
-
-// Exchange authorizes the session and returns an access token.
-func (c *Config) Exchange(code string) (*oauth2.Token, error) {
- t, err := c.config.Exchange(oauth2.NoContext, code)
- if err == nil {
- metrics.M.AuthExchange.WithLabelValues("microsoft").Inc()
- }
- return t, err
-}
-
-// Email retrieves the email address of the user.
-func (c *Config) Email(token *oauth2.Token) string {
- return c.getMe(token, "mail")
-}
-
-// Username retrieves the username portion of the user's email address.
-func (c *Config) Username(token *oauth2.Token) string {
- return strings.Split(c.Email(token), "@")[0]
-}
diff --git a/server/auth/microsoft/microsoft_test.go b/server/auth/microsoft/microsoft_test.go
deleted file mode 100644
index e362ef9..0000000
--- a/server/auth/microsoft/microsoft_test.go
+++ /dev/null
@@ -1,72 +0,0 @@
-package microsoft
-
-import (
- "fmt"
- "testing"
-
- "github.com/nsheridan/cashier/server/config"
- "github.com/stretchr/testify/assert"
-)
-
-var (
- oauthClientID = "id"
- oauthClientSecret = "secret"
- oauthCallbackURL = "url"
- tenant = "example.com"
- users = []string{"user"}
-)
-
-func TestNew(t *testing.T) {
- a := assert.New(t)
- p, err := newMicrosoft()
- a.NoError(err)
- a.Equal(p.config.ClientID, oauthClientID)
- a.Equal(p.config.ClientSecret, oauthClientSecret)
- a.Equal(p.config.RedirectURL, oauthCallbackURL)
- a.Equal(p.tenant, tenant)
- a.Equal(p.whitelist, map[string]bool{"user": true})
-}
-
-func TestWhitelist(t *testing.T) {
- c := &config.Auth{
- OauthClientID: oauthClientID,
- OauthClientSecret: oauthClientSecret,
- OauthCallbackURL: oauthCallbackURL,
- ProviderOpts: map[string]string{"tenant": ""},
- UsersWhitelist: []string{},
- }
- if _, err := New(c); err == nil {
- t.Error("creating a provider without a tenant set should return an error")
- }
- // Set a user whitelist but no tenant
- c.UsersWhitelist = users
- if _, err := New(c); err != nil {
- t.Error("creating a provider with users but no tenant should not return an error")
- }
- // Unset the user whitelist and set a tenant
- c.UsersWhitelist = []string{}
- c.ProviderOpts = map[string]string{"tenant": tenant}
- if _, err := New(c); err != nil {
- t.Error("creating a provider with a tenant set but without a user whitelist should not return an error")
- }
-}
-
-func TestStartSession(t *testing.T) {
- a := assert.New(t)
-
- p, err := newMicrosoft()
- a.NoError(err)
- s := p.StartSession("test_state")
- a.Contains(s, fmt.Sprintf("login.microsoftonline.com/%s/oauth2/v2.0/authorize", tenant))
-}
-
-func newMicrosoft() (*Config, error) {
- c := &config.Auth{
- OauthClientID: oauthClientID,
- OauthClientSecret: oauthClientSecret,
- OauthCallbackURL: oauthCallbackURL,
- ProviderOpts: map[string]string{"tenant": tenant},
- UsersWhitelist: users,
- }
- return New(c)
-}
diff --git a/server/auth/provider.go b/server/auth/provider.go
deleted file mode 100644
index 9d1c8bd..0000000
--- a/server/auth/provider.go
+++ /dev/null
@@ -1,13 +0,0 @@
-package auth
-
-import "golang.org/x/oauth2"
-
-// Provider is an abstraction of different auth methods.
-type Provider interface {
- Name() string
- StartSession(string) string
- Exchange(string) (*oauth2.Token, error)
- Username(*oauth2.Token) string
- Valid(*oauth2.Token) bool
- Revoke(*oauth2.Token) error
-}
diff --git a/server/auth/testprovider/testprovider.go b/server/auth/testprovider/testprovider.go
deleted file mode 100644
index f785081..0000000
--- a/server/auth/testprovider/testprovider.go
+++ /dev/null
@@ -1,56 +0,0 @@
-package testprovider
-
-import (
- "time"
-
- "github.com/nsheridan/cashier/server/auth"
-
- "golang.org/x/oauth2"
-)
-
-const (
- name = "testprovider"
-)
-
-// Config is an implementation of `auth.Provider` for testing.
-type Config struct{}
-
-var _ auth.Provider = (*Config)(nil)
-
-// New creates a new provider.
-func New() *Config {
- return &Config{}
-}
-
-// Name returns the name of the provider.
-func (c *Config) Name() string {
- return name
-}
-
-// Valid validates the oauth token.
-func (c *Config) Valid(token *oauth2.Token) bool {
- return true
-}
-
-// Revoke disables the access token.
-func (c *Config) Revoke(token *oauth2.Token) error {
- return nil
-}
-
-// StartSession retrieves an authentication endpoint.
-func (c *Config) StartSession(state string) string {
- return "https://www.example.com/auth"
-}
-
-// Exchange authorizes the session and returns an access token.
-func (c *Config) Exchange(code string) (*oauth2.Token, error) {
- return &oauth2.Token{
- AccessToken: "token",
- Expiry: time.Now().Add(1 * time.Hour),
- }, nil
-}
-
-// Username retrieves the username portion of the user's email address.
-func (c *Config) Username(token *oauth2.Token) string {
- return "test"
-}