Skip to content
Snippets Groups Projects
Commit a349115e authored by Kevin Lyda's avatar Kevin Lyda :speech_balloon:
Browse files

Initial pass at a gitlab module.

parent fbec71bc
No related branches found
No related tags found
No related merge requests found
......@@ -28,6 +28,7 @@ import (
"github.com/nsheridan/cashier/lib"
"github.com/nsheridan/cashier/server/auth"
"github.com/nsheridan/cashier/server/auth/github"
"github.com/nsheridan/cashier/server/auth/gitlab"
"github.com/nsheridan/cashier/server/auth/google"
"github.com/nsheridan/cashier/server/config"
"github.com/nsheridan/cashier/server/signer"
......@@ -383,6 +384,8 @@ func main() {
authprovider, err = google.New(conf.Auth)
case "github":
authprovider, err = github.New(conf.Auth)
case "gitlab":
authprovider, err = gitlab.New(conf.Auth)
default:
log.Fatalf("Unknown provider %s\n", conf.Auth.Provider)
}
......
package gitlab
import (
"errors"
"net/http"
"time"
"github.com/nsheridan/cashier/server/auth"
"github.com/nsheridan/cashier/server/config"
gitlabapi "github.com/xanzy/go-gitlab"
"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
organisation string
whitelist map[string]bool
allusers bool
}
// New creates a new Github provider from a configuration.
func New(c *config.Auth) (auth.Provider, error) {
uw := make(map[string]bool)
for _, u := range c.UsersWhitelist {
uw[u] = true
}
allUsers := false
if c.ProviderOpts["allusers"] == "true" {
allUsers = true
}
if !allUsers && c.ProviderOpts["organisation"] == "" && len(uw) == 0 {
return nil, errors.New("gitlab_opts organisation and the users whitelist must not be both empty if allusers isn't true")
}
if c.ProviderOpts["authurl"] == "" || c.ProviderOpts["tokenurl"] == "" {
return nil, errors.New("gitlab_opts authurl and tokenurl must be set")
}
return &Config{
config: &oauth2.Config{
ClientID: c.OauthClientID,
ClientSecret: c.OauthClientSecret,
RedirectURL: c.OauthCallbackURL,
Endpoint: oauth2.Endpoint{
AuthURL: c.ProviderOpts["authurl"],
TokenURL: c.ProviderOpts["tokenurl"],
},
Scopes: []string{
"api",
},
},
organisation: c.ProviderOpts["organisation"],
whitelist: uw,
allusers: allUsers,
}, 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 c.allusers {
return true
}
if len(c.whitelist) > 0 && !c.whitelist[c.Username(token)] {
return false
}
if !token.Valid() {
return false
}
if c.organisation == "" {
// There's no organisation and token is valid. Can only reach
// here if user whitelist is set and user is in whitelist.
return true
}
client := gitlabapi.NewClient(c.newClient(token), token.AccessToken)
groups, _, err := client.Groups.ListGroups(nil)
if err != nil {
return false
}
for _, g := range groups {
if g.Name == c.organisation {
return true
}
}
return false
}
// 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
}
// StartSession retrieves an authentication endpoint from Github.
func (c *Config) StartSession(state string) *auth.Session {
return &auth.Session{
AuthURL: 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 {
return nil, err
}
// Github tokens don't have an expiry. Set one so that the session expires
// after a period.
if t.Expiry.Unix() <= 0 {
t.Expiry = time.Now().Add(1 * time.Hour)
}
return t, nil
}
// Username retrieves the username portion of the user's email address.
func (c *Config) Username(token *oauth2.Token) string {
client := gitlabapi.NewClient(c.newClient(token), token.AccessToken)
u, _, err := client.Users.CurrentUser()
if err != nil {
return ""
}
return u.Username
}
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"
authurl = "https://exampleorg/oauth/authorize"
tokenurl = "https://exampleorg/oauth/token"
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 TestNewEmptyAuthURL(t *testing.T) {
authurl = ""
a := assert.New(t)
_, err := newGitlab()
a.EqualError(err, "gitlab_opts authurl and tokenurl must be set")
authurl = "https://exampleorg/oauth/authorize"
}
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, _ := newGithub()
s := p.StartSession("test_state")
a.Contains(s.AuthURL, "exampleorg/oauth/authorize")
a.Contains(s.AuthURL, "state=test_state")
a.Contains(s.AuthURL, 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,
"authurl": authurl,
"tokenurl": tokenurl,
},
}
return New(c)
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment