all providers implement interface

pull/1/head
Brad Rydzewski 6 years ago
parent c5678d82b0
commit 0e45116bc8

@ -12,7 +12,6 @@ import (
"github.com/drone/go-login/login"
"github.com/drone/go-login/login/bitbucket"
"github.com/drone/go-login/login/gitea"
"github.com/drone/go-login/login/github"
"github.com/drone/go-login/login/gitlab"
"github.com/drone/go-login/login/gogs"
@ -42,54 +41,51 @@ func main() {
var auther http.Handler
switch *provider {
case "gogs":
auther = gogs.New(
http.HandlerFunc(details),
gogs.WithAddress(*providerURL),
case "gogs", "gitea":
auther = gogs.New(*providerURL,
gogs.WithLoginRedirect("/login/form"),
)
case "gitea":
auther = gitea.New(
).Authorize(
http.HandlerFunc(details),
gitea.WithAddress(*providerURL),
gitea.WithLoginRedirect("/login/form"),
)
case "gitlab":
auther = gitlab.New(
http.HandlerFunc(details),
gitlab.WithClientID(*clientID),
gitlab.WithClientSecret(*clientSecret),
gitlab.WithRedirectURL(*redirectURL),
gitlab.WithScope("read_user", "api"),
).Authorize(
http.HandlerFunc(details),
)
case "github":
auther = github.New(
http.HandlerFunc(details),
github.WithClientID(*clientID),
github.WithClientSecret(*clientSecret),
github.WithScope("repo", "user", "read:org"),
).Authorize(
http.HandlerFunc(details),
)
case "bitbucket":
auther = bitbucket.New(
http.HandlerFunc(details),
bitbucket.WithClientID(*clientID),
bitbucket.WithClientSecret(*clientSecret),
bitbucket.WithRedirectURL(*redirectURL),
).Authorize(
http.HandlerFunc(details),
)
case "stash":
auther = stash.New(
http.HandlerFunc(details),
auther = stash.New(*providerURL,
stash.WithConsumerKey(*consumerKey),
stash.WithPrivateKeyFile(*consumerRsa),
stash.WithCallbackURL(*redirectURL),
stash.WithAddress(*providerURL),
).Authorize(
http.HandlerFunc(details),
)
}
// handles the authorization flow and displays the
// authorization results at completion.
http.Handle("/login", auther)
http.Handle("/login/form", http.HandlerFunc(form))
http.Handle("/login", auther)
// redirects the user to the login handler.
http.Handle("/", http.RedirectHandler("/login", http.StatusSeeOther))

@ -7,72 +7,84 @@ package bitbucket
import (
"net/http"
"github.com/drone/go-login/login"
"github.com/drone/go-login/login/internal/oauth2"
)
// Options provides the Bitbucket authentication options.
type Options struct {
redirectURL string
clientID string
clientSecret string
client *http.Client
}
func createOptions() *Options {
return &Options{
client: http.DefaultClient,
}
}
const (
accessTokenURL = "https://bitbucket.org/site/oauth2/access_token"
authorizationURL = "https://bitbucket.org/site/oauth2/authorize"
)
// Option configures an authorization handler option.
type Option func(o *Options)
type Option func(a *Authorizer)
// WithClient configures the authorization handler with a
// custom http.Client.
func WithClient(client *http.Client) Option {
return func(o *Options) {
o.client = client
return func(a *Authorizer) {
a.client = client
}
}
// WithClientID configures the authorization handler with
// the client_id.
func WithClientID(clientID string) Option {
return func(o *Options) {
o.clientID = clientID
return func(a *Authorizer) {
a.clientID = clientID
}
}
// WithClientSecret configures the authorization handler
// with the client_secret.
func WithClientSecret(clientSecret string) Option {
return func(o *Options) {
o.clientSecret = clientSecret
return func(a *Authorizer) {
a.clientSecret = clientSecret
}
}
// WithRedirectURL configures the authorization handler
// with the redirect_url
func WithRedirectURL(redirectURL string) Option {
return func(o *Options) {
o.redirectURL = redirectURL
return func(a *Authorizer) {
a.redirectURL = redirectURL
}
}
// Authorizer configures a Bitbucket auth provider.
type Authorizer struct {
redirectURL string
clientID string
clientSecret string
client *http.Client
}
func newDefault() *Authorizer {
return &Authorizer{
client: http.DefaultClient,
}
}
// New returns a http.Handler that runs h at the completion
// of the Bitbucket authorization flow. The Bitbucket
// authorization is passed to h in the http.Request context.
func New(h http.Handler, opt ...Option) http.Handler {
opts := createOptions()
for _, fn := range opt {
fn(opts)
// New returns a Bitbucket authorization provider.
func New(opts ...Option) login.Authorizer {
auther := newDefault()
for _, opt := range opts {
opt(auther)
}
return auther
}
// Authorize returns a http.Handler that runs h at the
// completion of the GitHub authorization flow. The GitHub
// authorization details are available to h in the
// http.Request context.
func (a *Authorizer) Authorize(h http.Handler) http.Handler {
return oauth2.Handler(h, &oauth2.Config{
Client: opts.client,
ClientID: opts.clientID,
ClientSecret: opts.clientSecret,
RedirectURL: opts.redirectURL,
AccessTokenURL: "https://bitbucket.org/site/oauth2/access_token",
AuthorizationURL: "https://bitbucket.org/site/oauth2/authorize",
Client: a.client,
ClientID: a.clientID,
ClientSecret: a.clientSecret,
RedirectURL: a.redirectURL,
AccessTokenURL: accessTokenURL,
AuthorizationURL: authorizationURL,
})
}

@ -11,40 +11,40 @@ import (
func TestWithClient(t *testing.T) {
c := &http.Client{}
o := &Options{}
WithClient(c)(o)
if got, want := o.client, c; got != want {
a := &Authorizer{}
WithClient(c)(a)
if got, want := a.client, c; got != want {
t.Errorf("Expect custom client")
}
}
func TestWithClientID(t *testing.T) {
opts := &Options{}
WithClientID("3da54155991")(opts)
if got, want := opts.clientID, "3da54155991"; got != want {
a := &Authorizer{}
WithClientID("3da54155991")(a)
if got, want := a.clientID, "3da54155991"; got != want {
t.Errorf("Expect custom client_id")
}
}
func TestWithClientSecret(t *testing.T) {
opts := &Options{}
WithClientSecret("5012f6c60b2")(opts)
if got, want := opts.clientSecret, "5012f6c60b2"; got != want {
a := &Authorizer{}
WithClientSecret("5012f6c60b2")(a)
if got, want := a.clientSecret, "5012f6c60b2"; got != want {
t.Errorf("Expect custom client_secret")
}
}
func TestWithRedirectURL(t *testing.T) {
opts := &Options{}
WithRedirectURL("http://company.com/login")(opts)
if got, want := opts.redirectURL, "http://company.com/login"; got != want {
a := &Authorizer{}
WithRedirectURL("http://company.com/login")(a)
if got, want := a.redirectURL, "http://company.com/login"; got != want {
t.Errorf("Expect custom redirect_uri")
}
}
func TestDefaultOptions(t *testing.T) {
opts := createOptions()
if got, want := opts.client, http.DefaultClient; got != want {
func TestDefaultAuthorizer(t *testing.T) {
a := newDefault()
if got, want := a.client, http.DefaultClient; got != want {
t.Errorf("Expect default client is http.DefaultClient")
}
}

@ -1,62 +0,0 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gitea
import (
"net/http"
"strings"
)
// Options provides the Gogs authentication options.
type Options struct {
label string
login string
server string
client *http.Client
}
func defaultOptions() *Options {
return &Options{
label: "default",
client: http.DefaultClient,
}
}
// Option configures an authorization handler option.
type Option func(o *Options)
// WithAddress configures the authorization handler with
// the progived Gogs server address.
func WithAddress(server string) Option {
return func(o *Options) {
o.server = strings.TrimPrefix(server, "/")
}
}
// WithClient configures the authorization handler with a
// custom http.Client.
func WithClient(client *http.Client) Option {
return func(o *Options) {
o.client = client
}
}
// WithTokenName configures the authorization handler to
// use the specificed token name when finding and creating
// authorization tokens.
func WithTokenName(name string) Option {
return func(o *Options) {
o.label = name
}
}
// WithLoginRedirect configures the authorization handler
// to redirect the http.Request to the login form when the
// username or password are missing from the Form data.
func WithLoginRedirect(path string) Option {
return func(o *Options) {
o.login = path
}
}

@ -1,39 +0,0 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gitea
import (
"net/http"
"testing"
)
func TestWithAddress(t *testing.T) {
h := New(nil, WithAddress("https://try.gitea.io"))
if got, want := h.(*handler).server, "https://try.gitea.io"; got != want {
t.Errorf("Expect server address %q, got %q", want, got)
}
}
func TestWithClient(t *testing.T) {
c := &http.Client{}
h := New(nil, WithClient(c))
if got, want := h.(*handler).client, c; got != want {
t.Errorf("Expect custom client")
}
}
func TestWithTokenName(t *testing.T) {
h := New(nil, WithTokenName("some_token"))
if got, want := h.(*handler).label, "some_token"; got != want {
t.Errorf("Expect token name url %q, got %q", want, got)
}
}
func TestWithLoginRedirect(t *testing.T) {
h := New(nil, WithLoginRedirect("/path/to/login"))
if got, want := h.(*handler).login, "/path/to/login"; got != want {
t.Errorf("Expect login redirect url %q, got %q", want, got)
}
}

@ -8,11 +8,13 @@ import (
"net/http"
"strings"
"github.com/drone/go-login/login"
"github.com/drone/go-login/login/internal/oauth2"
)
// Options provides the GitHub authentication options.
type Options struct {
// Authorizer configures a GitHub authorization
// provider.
type Authorizer struct {
scope []string
clientID string
clientSecret string
@ -20,20 +22,20 @@ type Options struct {
client *http.Client
}
func defaultOptions() *Options {
return &Options{
func newDefault() *Authorizer {
return &Authorizer{
server: "https://github.com",
client: http.DefaultClient,
}
}
// Option configures an authorization handler option.
type Option func(o *Options)
type Option func(a *Authorizer)
// WithClient configures the authorization handler with a
// custom http.Client.
func WithClient(client *http.Client) Option {
return func(o *Options) {
return func(o *Authorizer) {
o.client = client
}
}
@ -41,7 +43,7 @@ func WithClient(client *http.Client) Option {
// WithClientID configures the authorization handler with
// the client_id.
func WithClientID(clientID string) Option {
return func(o *Options) {
return func(o *Authorizer) {
o.clientID = clientID
}
}
@ -49,7 +51,7 @@ func WithClientID(clientID string) Option {
// WithClientSecret configures the authorization handler
// with the client_secret.
func WithClientSecret(clientSecret string) Option {
return func(o *Options) {
return func(o *Authorizer) {
o.clientSecret = clientSecret
}
}
@ -57,7 +59,7 @@ func WithClientSecret(clientSecret string) Option {
// WithScope configures the authorization handler with the
// these scopes.
func WithScope(scope ...string) Option {
return func(o *Options) {
return func(o *Authorizer) {
o.scope = scope
}
}
@ -65,26 +67,32 @@ func WithScope(scope ...string) Option {
// WithAddress configures the authorization handler with
// a GitHub enterprise server address.
func WithAddress(server string) Option {
return func(o *Options) {
return func(o *Authorizer) {
o.server = strings.TrimSuffix(server, "/")
}
}
// New returns a http.Handler that runs h at the completion
// of the GitHub authorization flow. The GitHub authorization
// is availabe to h in the http.Request context.
func New(h http.Handler, opt ...Option) http.Handler {
opts := defaultOptions()
for _, fn := range opt {
fn(opts)
// New returns a GitHub authorization provider.
func New(opts ...Option) login.Authorizer {
v := newDefault()
for _, opt := range opts {
opt(v)
}
return v
}
// Authorize returns a http.Handler that runs h at the
// completion of the GitHub authorization flow. The GitHub
// authorization details are available to h in the
// http.Request context.
func (a *Authorizer) Authorize(h http.Handler) http.Handler {
return oauth2.Handler(h, &oauth2.Config{
BasicAuthOff: true,
Client: opts.client,
ClientID: opts.clientID,
ClientSecret: opts.clientSecret,
AccessTokenURL: opts.server + "/login/oauth/access_token",
AuthorizationURL: opts.server + "/login/oauth/authorize",
Scope: opts.scope,
Client: a.client,
ClientID: a.clientID,
ClientSecret: a.clientSecret,
AccessTokenURL: a.server + "/login/oauth/access_token",
AuthorizationURL: a.server + "/login/oauth/authorize",
Scope: a.scope,
})
}

@ -13,54 +13,54 @@ import (
func TestWithClient(t *testing.T) {
c := &http.Client{}
o := &Options{}
WithClient(c)(o)
if got, want := o.client, c; got != want {
v := &Authorizer{}
WithClient(c)(v)
if got, want := v.client, c; got != want {
t.Errorf("Expect custom client option applied")
}
}
func TestWithClientID(t *testing.T) {
opts := &Options{}
WithClientID("3da54155991")(opts)
if got, want := opts.clientID, "3da54155991"; got != want {
v := &Authorizer{}
WithClientID("3da54155991")(v)
if got, want := v.clientID, "3da54155991"; got != want {
t.Errorf("Expect client_id option applied")
}
}
func TestWithClientSecret(t *testing.T) {
opts := &Options{}
WithClientSecret("5012f6c60b2")(opts)
if got, want := opts.clientSecret, "5012f6c60b2"; got != want {
v := &Authorizer{}
WithClientSecret("5012f6c60b2")(v)
if got, want := v.clientSecret, "5012f6c60b2"; got != want {
t.Errorf("Expect client_secret option applied")
}
}
func TestWithAddress(t *testing.T) {
opts := &Options{}
WithAddress("https://company.github.com/")(opts)
if strings.HasSuffix(opts.server, "/") {
v := &Authorizer{}
WithAddress("https://company.github.com/")(v)
if strings.HasSuffix(v.server, "/") {
t.Errorf("Expect trailing slash removed from server address")
}
if got, want := opts.server, "https://company.github.com"; got != want {
if got, want := v.server, "https://company.github.com"; got != want {
t.Errorf("Expect server address option applied")
}
}
func TestWithScope(t *testing.T) {
opts := &Options{}
WithScope("user", "repo")(opts)
if got, want := opts.scope, []string{"user", "repo"}; !reflect.DeepEqual(got, want) {
v := &Authorizer{}
WithScope("user", "repo")(v)
if got, want := v.scope, []string{"user", "repo"}; !reflect.DeepEqual(got, want) {
t.Errorf("Expect scope option applied")
}
}
func TestDefaultOptions(t *testing.T) {
opts := defaultOptions()
if got, want := opts.client, http.DefaultClient; got != want {
func TestDefaultAuthorizer(t *testing.T) {
v := newDefault()
if got, want := v.client, http.DefaultClient; got != want {
t.Errorf("Expect default client is http.DefaultClient")
}
if got, want := opts.server, "https://github.com"; got != want {
if got, want := v.server, "https://github.com"; got != want {
t.Errorf("Expect default server is https://github.com")
}
}

@ -8,11 +8,12 @@ import (
"net/http"
"strings"
"github.com/drone/go-login/login"
"github.com/drone/go-login/login/internal/oauth2"
)
// Options provides the GitLab authentication options.
type Options struct {
// Authorizer configures the GitLab auth provider.
type Authorizer struct {
scope []string
clientID string
clientSecret string
@ -21,80 +22,88 @@ type Options struct {
client *http.Client
}
func createOptions() *Options {
return &Options{
func newDefault() *Authorizer {
return &Authorizer{
server: "https://gitlab.com",
client: http.DefaultClient,
}
}
// Option configures an authorization handler option.
type Option func(o *Options)
type Option func(a *Authorizer)
// WithClient configures the authorization handler with a
// custom http.Client.
func WithClient(client *http.Client) Option {
return func(o *Options) {
o.client = client
return func(a *Authorizer) {
a.client = client
}
}
// WithClientID configures the authorization handler with
// the client_id.
func WithClientID(clientID string) Option {
return func(o *Options) {
o.clientID = clientID
return func(a *Authorizer) {
a.clientID = clientID
}
}
// WithClientSecret configures the authorization handler
// with the client_secret.
func WithClientSecret(clientSecret string) Option {
return func(o *Options) {
o.clientSecret = clientSecret
return func(a *Authorizer) {
a.clientSecret = clientSecret
}
}
// WithRedirectURL configures the authorization handler
// with the redirect_url
func WithRedirectURL(redirectURL string) Option {
return func(o *Options) {
o.redirectURL = redirectURL
return func(a *Authorizer) {
a.redirectURL = redirectURL
}
}
// WithScope configures the authorization handler with the
// these scopes.
func WithScope(scope ...string) Option {
return func(o *Options) {
o.scope = scope
return func(a *Authorizer) {
a.scope = scope
}
}
// WithAddress configures the authorization handler with
// the the self-hosted GitLab server address.
func WithAddress(server string) Option {
return func(o *Options) {
o.server = strings.TrimSuffix(server, "/")
return func(a *Authorizer) {
if server != "" {
a.server = strings.TrimSuffix(server, "/")
}
}
}
// New returns a http.Handler that runs h at the completion
// of the GitLab authorization flow. The GitLab authorization
// is availabe to h in the http.Request context.
func New(h http.Handler, opt ...Option) http.Handler {
opts := createOptions()
for _, fn := range opt {
fn(opts)
// New returns a GitLab authorization provider.
func New(opts ...Option) login.Authorizer {
auther := newDefault()
for _, opt := range opts {
opt(auther)
}
return auther
}
// Authorize returns a http.Handler that runs h at the
// completion of the GitLab authorization flow. The GitLab
// authorization details are available to h in the
// http.Request context.
func (a *Authorizer) Authorize(h http.Handler) http.Handler {
return oauth2.Handler(h, &oauth2.Config{
BasicAuthOff: true,
Client: opts.client,
ClientID: opts.clientID,
ClientSecret: opts.clientSecret,
RedirectURL: opts.redirectURL,
AccessTokenURL: opts.server + "/oauth/token",
AuthorizationURL: opts.server + "/oauth/authorize",
Scope: opts.scope,
Client: a.client,
ClientID: a.clientID,
ClientSecret: a.clientSecret,
RedirectURL: a.redirectURL,
AccessTokenURL: a.server + "/oauth/token",
AuthorizationURL: a.server + "/oauth/authorize",
Scope: a.scope,
})
}

@ -13,62 +13,62 @@ import (
func TestWithClient(t *testing.T) {
c := &http.Client{}
o := &Options{}
WithClient(c)(o)
if got, want := o.client, c; got != want {
a := &Authorizer{}
WithClient(c)(a)
if got, want := a.client, c; got != want {
t.Errorf("Expect custom client option applied")
}
}
func TestWithClientID(t *testing.T) {
opts := &Options{}
WithClientID("3da54155991")(opts)
if got, want := opts.clientID, "3da54155991"; got != want {
a := &Authorizer{}
WithClientID("3da54155991")(a)
if got, want := a.clientID, "3da54155991"; got != want {
t.Errorf("Expect client_id option applied")
}
}
func TestWithClientSecret(t *testing.T) {
opts := &Options{}
WithClientSecret("5012f6c60b2")(opts)
if got, want := opts.clientSecret, "5012f6c60b2"; got != want {
a := &Authorizer{}
WithClientSecret("5012f6c60b2")(a)
if got, want := a.clientSecret, "5012f6c60b2"; got != want {
t.Errorf("Expect client_secret option applied")
}
}
func TestWithRedirectURL(t *testing.T) {
opts := &Options{}
WithRedirectURL("http://company.com/login")(opts)
if got, want := opts.redirectURL, "http://company.com/login"; got != want {
a := &Authorizer{}
WithRedirectURL("http://company.com/login")(a)
if got, want := a.redirectURL, "http://company.com/login"; got != want {
t.Errorf("Expect redirect_uri option applied")
}
}
func TestWithAddress(t *testing.T) {
opts := &Options{}
WithAddress("https://company.gitlab.com/")(opts)
if strings.HasSuffix(opts.server, "/") {
a := &Authorizer{}
WithAddress("https://company.gitlab.com/")(a)
if strings.HasSuffix(a.server, "/") {
t.Errorf("Expect trailing slash removed from server address")
}
if got, want := opts.server, "https://company.gitlab.com"; got != want {
if got, want := a.server, "https://company.gitlab.com"; got != want {
t.Errorf("Expect server address option applied")
}
}
func TestWithScope(t *testing.T) {
opts := &Options{}
WithScope("read_user", "api")(opts)
if got, want := opts.scope, []string{"read_user", "api"}; !reflect.DeepEqual(got, want) {
a := &Authorizer{}
WithScope("read_user", "api")(a)
if got, want := a.scope, []string{"read_user", "api"}; !reflect.DeepEqual(got, want) {
t.Errorf("Expect scope option applied")
}
}
func TestDefaultOptions(t *testing.T) {
opts := createOptions()
if got, want := opts.client, http.DefaultClient; got != want {
func TestDefaultAuthorizer(t *testing.T) {
a := newDefault()
if got, want := a.client, http.DefaultClient; got != want {
t.Errorf("Expect default client is http.DefaultClient")
}
if got, want := opts.server, "https://gitlab.com"; got != want {
if got, want := a.server, "https://gitlab.com"; got != want {
t.Errorf("Expect default server is https://gitlab.com")
}
}

@ -5,129 +5,76 @@
package gogs
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
"strings"
"github.com/drone/go-login/login"
)
type token struct {
Name string `json:"name"`
Sha1 string `json:"sha1,omitempty"`
}
type handler struct {
next http.Handler
// Authorizer configures the Gogs auth provider.
type Authorizer struct {
label string
login string
server string
client *http.Client
}
// New returns a Handler that runs h at the completion of
// the gogs authorization flow. The gogs authorization
// is availabe to h in the http.Request context.
func New(h http.Handler, opt ...Option) http.Handler {
opts := defaultOptions()
for _, fn := range opt {
fn(opts)
}
return &handler{
next: h,
label: opts.label,
login: opts.login,
server: opts.server,
client: opts.client,
func newDefault() *Authorizer {
return &Authorizer{
label: "default",
client: http.DefaultClient,
}
}
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
user := r.FormValue("username")
pass := r.FormValue("password")
if (user == "" || pass == "") && h.login != "" {
http.Redirect(w, r, h.login, 303)
return
}
token, err := h.createFindToken(user, pass)
if err != nil {
ctx = login.WithError(ctx, err)
} else {
ctx = login.WithToken(ctx, &login.Token{
Access: token.Sha1,
})
}
h.next.ServeHTTP(w, r.WithContext(ctx))
}
// Option configures an authorization handler option.
type Option func(a *Authorizer)
func (h *handler) createFindToken(user, pass string) (*token, error) {
tokens, err := h.findTokens(user, pass)
if err != nil {
return nil, err
// WithClient configures the authorization handler with a
// custom http.Client.
func WithClient(client *http.Client) Option {
return func(a *Authorizer) {
a.client = client
}
for _, token := range tokens {
if token.Name == h.label {
return token, nil
}
}
return h.createToken(user, pass)
}
func (h *handler) createToken(user, pass string) (*token, error) {
path := fmt.Sprintf("%s/api/v1/users/%s/tokens", h.server, user)
buf := new(bytes.Buffer)
json.NewEncoder(buf).Encode(&token{
Name: h.label,
})
req, err := http.NewRequest("POST", path, buf)
if err != nil {
return nil, err
// WithTokenName configures the authorization handler to
// use the specificed token name when finding and creating
// authorization tokens.
func WithTokenName(name string) Option {
return func(a *Authorizer) {
a.label = name
}
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth(user, pass)
}
res, err := h.client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode > 299 {
return nil, errors.New(
http.StatusText(res.StatusCode),
)
// WithLoginRedirect configures the authorization handler
// to redirect the http.Request to the login form when the
// username or password are missing from the Form data.
func WithLoginRedirect(path string) Option {
return func(a *Authorizer) {
a.login = path
}
out := new(token)
err = json.NewDecoder(res.Body).Decode(out)
return out, err
}
func (h *handler) findTokens(user, pass string) ([]*token, error) {
path := fmt.Sprintf("%s/api/v1/users/%s/tokens", h.server, user)
req, err := http.NewRequest("GET", path, nil)
if err != nil {
return nil, err
// New returns a Gogs authorization provider.
func New(address string, opts ...Option) login.Authorizer {
auther := newDefault()
auther.server = strings.TrimSuffix(address, "/")
for _, opt := range opts {
opt(auther)
}
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth(user, pass)
return auther
}
res, err := h.client.Do(req)
if err != nil {
return nil, err
}
defer res.Body.Close()
if res.StatusCode > 299 {
return nil, errors.New(
http.StatusText(res.StatusCode),
)
// Authorize returns a http.Handler that runs h at the
// completion of the GitLab authorization flow. The GitLab
// authorization details are available to h in the
// http.Request context.
func (a *Authorizer) Authorize(h http.Handler) http.Handler {
return &handler{
next: h,
label: a.label,
login: a.login,
server: a.server,
client: a.client,
}
out := []*token{}
err = json.NewDecoder(res.Body).Decode(&out)
return out, err
}

@ -5,154 +5,28 @@
package gogs
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"github.com/drone/go-login/login"
"github.com/h2non/gock"
)
func TestLogin(t *testing.T) {
defer gock.Off()
tests := []struct {
user string
pass string
path string
auth string
tokens []*token
token *token
err error
}{
// Success, match found.
{
user: "janedoe",
pass: "password",
path: "/api/v1/users/janedoe/token",
auth: "Basic amFuZWRvZTpwYXNzd29yZA==",
token: &token{Name: "default", Sha1: "3da541559"},
tokens: []*token{{Name: "default", Sha1: "3da541559"}},
},
// Success, match not found, token created.
{
user: "janedoe",
pass: "password",
path: "/api/v1/users/janedoe/token",
auth: "Basic amFuZWRvZTpwYXNzd29yZA==",
token: &token{Name: "default", Sha1: "918a808c2"},
tokens: []*token{},
},
// Failure, error getting token list.
{
user: "janedoe",
pass: "password",
path: "/api/v1/users/janedoe/token",
auth: "Basic amFuZWRvZTpwYXNzd29yZA==",
tokens: nil,
token: nil,
err: errors.New("Not Found"),
},
// Failure, match not found, error creating token.
{
user: "janedoe",
pass: "password",
path: "/api/v1/users/janedoe/token",
auth: "Basic amFuZWRvZTpwYXNzd29yZA==",
tokens: []*token{{Name: "some-random-token-name", Sha1: "918a808c2"}},
token: nil,
err: errors.New("Not Found"),
},
func TestWithClient(t *testing.T) {
c := &http.Client{}
h := New("https://try.gogs.io", WithClient(c))
if got, want := h.(*Authorizer).client, c; got != want {
t.Errorf("Expect custom client")
}
}
for _, test := range tests {
gock.Flush()
if test.tokens != nil {
gock.New("https://gogs.io").
Get("/api/v1/users/janedoe/token").
MatchHeader("Authorization", test.auth).
Reply(200).
JSON(test.tokens)
} else {
gock.New("https://gogs.io").
Get("/api/v1/users/janedoe/token").
Reply(404)
}
if test.token != nil {
gock.New("https://gogs.io").
Post("/api/v1/users/janedoe/token").
MatchHeader("Authorization", test.auth).
Reply(200).
JSON(test.token)
} else {
gock.New("https://gogs.io").
Post("/api/v1/users/janedoe/token").
Reply(404)
}
var ctx context.Context
fn := func(w http.ResponseWriter, r *http.Request) {
ctx = r.Context()
}
h := New(
http.HandlerFunc(fn),
WithAddress("https://gogs.io"),
)
data := url.Values{
"username": {test.user},
"password": {test.pass},
}.Encode()
res := httptest.NewRecorder()
req := httptest.NewRequest("POST", "/", strings.NewReader(data))
req.Header.Set(
"Content-Type", "application/x-www-form-urlencoded",
)
h.ServeHTTP(res, req)
tok := login.TokenFrom(ctx)
err := login.ErrorFrom(ctx)
if test.err != nil {
if err == nil {
t.Errorf("Want error")
} else if got, want := err.Error(), test.err.Error(); got != want {
t.Errorf("Want error %q, got %q", want, got)
}
} else {
if tok == nil {
t.Errorf("Want user token, got nil")
} else if got, want := tok.Access, test.token.Sha1; got != want {
t.Errorf("Want access token %s, got %s", want, got)
}
}
func TestWithTokenName(t *testing.T) {
h := New("https://try.gogs.io", WithTokenName("some_token"))
if got, want := h.(*Authorizer).label, "some_token"; got != want {
t.Errorf("Expect token name url %q, got %q", want, got)
}
}
func TestLoginRedirect(t *testing.T) {
h := New(
http.NotFoundHandler(),
WithLoginRedirect("/login/form"),
WithAddress("https://gogs.io"),
).(*handler)
r := httptest.NewRequest("POST", "/login", nil)
w := httptest.NewRecorder()
h.ServeHTTP(w, r)
if want, got := w.Code, 303; want != got {
t.Errorf("Want status code %d, got %d", want, got)
}
if want, got := w.Header().Get("Location"), "/login/form"; want != got {
t.Errorf("Want redirect location %s, got %s", want, got)
func TestWithLoginRedirect(t *testing.T) {
h := New("https://try.gogs.io", WithLoginRedirect("/path/to/login"))
if got, want := h.(*Authorizer).login, "/path/to/login"; got != want {
t.Errorf("Expect login redirect url %q, got %q", want, got)
}
}

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gitea
package gogs
import (
"bytes"
@ -27,23 +27,6 @@ type handler struct {
client *http.Client
}
// New returns a Handler that runs h at the completion of
// the gitea authorization flow. The gitea authorization
// is availabe to h in the http.Request context.
func New(h http.Handler, opt ...Option) http.Handler {
opts := defaultOptions()
for _, fn := range opt {
fn(opts)
}
return &handler{
next: h,
label: opts.label,
login: opts.login,
server: opts.server,
client: opts.client,
}
}
func (h *handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
user := r.FormValue("username")

@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gitea
package gogs
import (
"context"
@ -73,25 +73,25 @@ func TestLogin(t *testing.T) {
gock.Flush()
if test.tokens != nil {
gock.New("https://gitea.io").
gock.New("https://gogs.io").
Get("/api/v1/users/janedoe/token").
MatchHeader("Authorization", test.auth).
Reply(200).
JSON(test.tokens)
} else {
gock.New("https://gitea.io").
gock.New("https://gogs.io").
Get("/api/v1/users/janedoe/token").
Reply(404)
}
if test.token != nil {
gock.New("https://gitea.io").
gock.New("https://gogs.io").
Post("/api/v1/users/janedoe/token").
MatchHeader("Authorization", test.auth).
Reply(200).
JSON(test.token)
} else {
gock.New("https://gitea.io").
gock.New("https://gogs.io").
Post("/api/v1/users/janedoe/token").
Reply(404)
}
@ -101,9 +101,8 @@ func TestLogin(t *testing.T) {
ctx = r.Context()
}
h := New(
h := New("https://gogs.io").Authorize(
http.HandlerFunc(fn),
WithAddress("https://gitea.io"),
)
data := url.Values{
@ -139,11 +138,11 @@ func TestLogin(t *testing.T) {
}
func TestLoginRedirect(t *testing.T) {
h := New(
http.NotFoundHandler(),
h := New("https://gogs.io",
WithLoginRedirect("/login/form"),
WithAddress("https://gitea.io"),
).(*handler)
).Authorize(
http.NotFoundHandler(),
)
r := httptest.NewRequest("POST", "/login", nil)
w := httptest.NewRecorder()

@ -1,62 +0,0 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gogs
import (
"net/http"
"strings"
)
// Options provides the Gogs authentication options.
type Options struct {
label string
login string
server string
client *http.Client
}
func defaultOptions() *Options {
return &Options{
label: "default",
client: http.DefaultClient,
}
}
// Option configures an authorization handler option.
type Option func(o *Options)
// WithAddress configures the authorization handler with
// the progived Gogs server address.
func WithAddress(server string) Option {
return func(o *Options) {
o.server = strings.TrimPrefix(server, "/")
}
}
// WithClient configures the authorization handler with a
// custom http.Client.
func WithClient(client *http.Client) Option {
return func(o *Options) {
o.client = client
}
}
// WithTokenName configures the authorization handler to
// use the specificed token name when finding and creating
// authorization tokens.
func WithTokenName(name string) Option {
return func(o *Options) {
o.label = name
}
}
// WithLoginRedirect configures the authorization handler
// to redirect the http.Request to the login form when the
// username or password are missing from the Form data.
func WithLoginRedirect(path string) Option {
return func(o *Options) {
o.login = path
}
}

@ -1,39 +0,0 @@
// Copyright 2017 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gogs
import (
"net/http"
"testing"
)
func TestWithAddress(t *testing.T) {
h := New(nil, WithAddress("https://try.gogs.io"))
if got, want := h.(*handler).server, "https://try.gogs.io"; got != want {
t.Errorf("Expect server address %q, got %q", want, got)
}
}
func TestWithClient(t *testing.T) {
c := &http.Client{}
h := New(nil, WithClient(c))
if got, want := h.(*handler).client, c; got != want {
t.Errorf("Expect custom client")
}
}
func TestWithTokenName(t *testing.T) {
h := New(nil, WithTokenName("some_token"))
if got, want := h.(*handler).label, "some_token"; got != want {
t.Errorf("Expect token name url %q, got %q", want, got)
}
}
func TestWithLoginRedirect(t *testing.T) {
h := New(nil, WithLoginRedirect("/path/to/login"))
if got, want := h.(*handler).login, "/path/to/login"; got != want {
t.Errorf("Expect login redirect url %q, got %q", want, got)
}
}

@ -6,9 +6,27 @@ package login
import (
"context"
"net/http"
"time"
)
// Authorizer returns a http.Handler that runs h at the
// completion of the authorization flow. The authorization
// results are available to h in the http.Request context.
type Authorizer interface {
Authorize(h http.Handler) http.Handler
}
// Middleware defines a Login middleware. The middleware
// wraps the http.Handler and intercepts the http.Request
// to perform authentication. The http.Handler is invoked
// when authentication is complete, with authentication
// details (oauth token, etc) passed to the handler via
// the http.Request context.
type Middleware interface {
Wrap(h http.Handler) http.Handler
}
// Token represents an authorization token.
type Token struct {
Access string

@ -12,6 +12,7 @@ import (
"net/http"
"strings"
"github.com/drone/go-login/login"
"github.com/drone/go-login/login/internal/oauth1"
)
@ -21,63 +22,38 @@ const (
accessTokenURL = "%s/plugins/servlet/oauth/access-token"
)
// Options provides the Bitbucket Server (Stash)
// authentication options.
type Options struct {
callbackURL string
address string
consumerKey string
consumerSecret string
signer oauth1.Signer
client *http.Client
}
func createOptions() *Options {
return &Options{
client: http.DefaultClient,
}
}
// Option configures an authorization handler option.
type Option func(o *Options)
type Option func(a *Authorizer)
// WithClient configures the authorization handler with a
// custom http.Client.
func WithClient(client *http.Client) Option {
return func(o *Options) {
o.client = client
return func(a *Authorizer) {
a.client = client
}
}
// WithConsumerKey configures the authorization handler with
// the oauth_consumer_key.
func WithConsumerKey(consumerKey string) Option {
return func(o *Options) {
o.consumerKey = consumerKey
return func(a *Authorizer) {
a.consumerKey = consumerKey
}
}
// WithConsumerSecret configures the authorization handler
// with the oauth_consumer_secret.
func WithConsumerSecret(consumerSecret string) Option {
return func(o *Options) {
o.consumerSecret = consumerSecret
return func(a *Authorizer) {
a.consumerSecret = consumerSecret
}
}
// WithCallbackURL configures the authorization handler
// with the oauth_callback_url
func WithCallbackURL(callbackURL string) Option {
return func(o *Options) {
o.callbackURL = callbackURL
}
}
// WithAddress configures the authorization handler with
// the Bitbucket Server address.
func WithAddress(address string) Option {
return func(o *Options) {
o.address = strings.TrimPrefix(address, "/")
return func(a *Authorizer) {
a.callbackURL = callbackURL
}
}
@ -94,32 +70,56 @@ func WithPrivateKeyFile(path string) Option {
// WithPrivateKey configures the authorization handler
// with the oauth private rsa key for signing requests.
func WithPrivateKey(data []byte) Option {
return func(o *Options) {
return func(a *Authorizer) {
p, _ := pem.Decode(data)
k, err := x509.ParsePKCS1PrivateKey(p.Bytes)
if err != nil {
panic(err)
}
o.signer = &oauth1.RSASigner{PrivateKey: k}
a.signer = &oauth1.RSASigner{PrivateKey: k}
}
}
// New returns a http.Handler that runs h at the completion
// of the Bitbucket authorization flow. The Bitbucket
// authorization is passed to h in the http.Request context.
func New(h http.Handler, opt ...Option) http.Handler {
opts := createOptions()
for _, fn := range opt {
fn(opts)
// Authorizer configures the Bitbucket Server (Stash)
// authorization provider.
type Authorizer struct {
callbackURL string
address string
consumerKey string
consumerSecret string
signer oauth1.Signer
client *http.Client
}
func newDefault() *Authorizer {
return &Authorizer{
client: http.DefaultClient,
}
}
// New returns a Bitbucket Server authorization provider.
func New(address string, opts ...Option) login.Authorizer {
auther := newDefault()
auther.address = strings.TrimPrefix(address, "/")
for _, opt := range opts {
opt(auther)
}
return auther
}
// Authorize returns a http.Handler that runs h at the
// completion of the GitHub authorization flow. The GitHub
// authorization details are available to h in the
// http.Request context.
func (a *Authorizer) Authorize(h http.Handler) http.Handler {
return oauth1.Handler(h, &oauth1.Config{
Signer: opts.signer,
Client: opts.client,
ConsumerKey: opts.consumerKey,
ConsumerSecret: opts.consumerSecret,
CallbackURL: opts.callbackURL,
AccessTokenURL: fmt.Sprintf(accessTokenURL, opts.address),
AuthorizationURL: fmt.Sprintf(authorizeTokenURL, opts.address),
RequestTokenURL: fmt.Sprintf(requestTokenURL, opts.address),
Signer: a.signer,
Client: a.client,
ConsumerKey: a.consumerKey,
ConsumerSecret: a.consumerSecret,
CallbackURL: a.callbackURL,
AccessTokenURL: fmt.Sprintf(accessTokenURL, a.address),
AuthorizationURL: fmt.Sprintf(authorizeTokenURL, a.address),
RequestTokenURL: fmt.Sprintf(requestTokenURL, a.address),
})
}

@ -0,0 +1,5 @@
// Copyright 2018 Drone.IO Inc. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package stash
Loading…
Cancel
Save