Skip to content

Commit

Permalink
fix: add expiry date/time to token strategy
Browse files Browse the repository at this point in the history
  • Loading branch information
shaj13 committed Oct 17, 2020
1 parent 3a9dc08 commit f509a3b
Show file tree
Hide file tree
Showing 7 changed files with 43 additions and 67 deletions.
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ linters:

linters-settings:
lll:
line-length: 110
line-length: 120
goimports:
local-prefixes: "github.com/shaj13/go-guardian/v2"
issues:
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
[![Coverage Status](https://coveralls.io/repos/github/shaj13/go-guardian/badge.svg?branch=master)](https://coveralls.io/github/shaj13/go-guardian?branch=master)
[![CircleCI](https://circleci.com/gh/shaj13/go-guardian/tree/master.svg?style=svg)](https://circleci.com/gh/shaj13/go-guardian/tree/master)

| :exclamation: Cache package has been moved to [libcache](https://github.com/shaj13/libcache) repository |
|----------------------------------------------------------------------------------------------------------|

# Go-Guardian
Go-Guardian is a golang library that provides a simple, clean, and idiomatic way to create powerful modern API and web authentication.

Expand Down Expand Up @@ -30,7 +33,6 @@ you want to focus on building awesome software. go-guardian is here to help with
Here are a few bullet point reasons you might like to try it out:
* provides simple, clean, and idiomatic API.
* provides top trends and traditional authentication methods.
* provides a package to caches the authentication decisions, based on different mechanisms and algorithms.
* provides two-factor authentication and one-time password as defined in [RFC-4226](https://tools.ietf.org/html/rfc4226) and [RFC-6238](https://tools.ietf.org/html/rfc6238)
* provides a mechanism to customize strategies, even enables writing a custom strategy

Expand Down
23 changes: 13 additions & 10 deletions auth/strategies/kubernetes/kubernetes.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"io/ioutil"
"net/http"
"strings"
"time"

kubeauth "k8s.io/api/authentication/v1"
kubemeta "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -29,7 +30,9 @@ type kubeReview struct {
client *http.Client
}

func (k *kubeReview) authenticate(ctx context.Context, r *http.Request, token string) (auth.Info, error) {
func (k *kubeReview) authenticate(ctx context.Context, r *http.Request, token string) (auth.Info, time.Time, error) {
var t time.Time

tr := &kubeauth.TokenReview{
Spec: kubeauth.TokenReviewSpec{
Token: token,
Expand All @@ -39,7 +42,7 @@ func (k *kubeReview) authenticate(ctx context.Context, r *http.Request, token st

body, err := json.Marshal(tr)
if err != nil {
return nil, fmt.Errorf(
return nil, t, fmt.Errorf(
"strategies/kubernetes: Failed to Marshal TokenReview Err: %s",
err,
)
Expand All @@ -49,7 +52,7 @@ func (k *kubeReview) authenticate(ctx context.Context, r *http.Request, token st

req, err := http.NewRequest(http.MethodPost, url, bytes.NewBuffer(body))
if err != nil {
return nil, err
return nil, t, err
}

req.Header.Set("Authorization", "Bearer "+k.token)
Expand All @@ -58,12 +61,12 @@ func (k *kubeReview) authenticate(ctx context.Context, r *http.Request, token st

resp, err := k.client.Do(req)
if err != nil {
return nil, err
return nil, t, err
}

body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
return nil, t, err
}

defer resp.Body.Close()
Expand All @@ -72,24 +75,24 @@ func (k *kubeReview) authenticate(ctx context.Context, r *http.Request, token st
status := &kubemeta.Status{}
err = json.Unmarshal(body, status)
if err == nil && status.Status != kubemeta.StatusSuccess {
return nil, fmt.Errorf("strategies/kubernetes: %s", status.Message)
return nil, t, fmt.Errorf("strategies/kubernetes: %s", status.Message)
}

tr = &kubeauth.TokenReview{}
err = json.Unmarshal(body, tr)
if err != nil {
return nil, fmt.Errorf(
return nil, t, fmt.Errorf(
"strategies/kubernetes: Failed to Unmarshal Response body to TokenReview Err: %s",
err,
)
}

if len(tr.Status.Error) > 0 {
return nil, fmt.Errorf("strategies/kubernetes: %s", tr.Status.Error)
return nil, t, fmt.Errorf("strategies/kubernetes: %s", tr.Status.Error)
}

if !tr.Status.Authenticated {
return nil, fmt.Errorf("strategies/kubernetes: Token Unauthorized")
return nil, t, fmt.Errorf("strategies/kubernetes: Token Unauthorized")
}

user := tr.Status.User
Expand All @@ -98,7 +101,7 @@ func (k *kubeReview) authenticate(ctx context.Context, r *http.Request, token st
extensions[k] = v
}

return auth.NewUserInfo(user.Username, user.UID, user.Groups, extensions), nil
return auth.NewUserInfo(user.Username, user.UID, user.Groups, extensions), t, nil
}

// GetAuthenticateFunc return function to authenticate request using kubernetes token review.
Expand Down
2 changes: 1 addition & 1 deletion auth/strategies/kubernetes/kubernetes_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func TestKubeReview(t *testing.T) {
client: srv.Client(),
}
r, _ := http.NewRequest("", "", nil)
info, err := kr.authenticate(r.Context(), r, "")
info, _, err := kr.authenticate(r.Context(), r, "")

assert.Equal(t, tt.err, err)
assert.Equal(t, tt.info, info)
Expand Down
33 changes: 12 additions & 21 deletions auth/strategies/token/cached.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,18 @@ package token
import (
"context"
"net/http"
"time"

"github.com/shaj13/go-guardian/v2/auth"
)

// AuthenticateFunc declare custom function to authenticate request using token.
// The authenticate function invoked by Authenticate Strategy method when
// The token does not exist in the cahce and the invocation result will be cached, unless an error returned.
// Use NoOpAuthenticate instead to refresh/mangae token directly using cache or Append function.
type AuthenticateFunc func(ctx context.Context, r *http.Request, token string) (auth.Info, error)
// AuthenticateFunc declare function signature to authenticate request using token.
// Any function that has the appropriate signature can be registered to the token strategy.
// AuthenticateFunc must return authenticated user info and token expiry time, otherwise error.
type AuthenticateFunc func(ctx context.Context, r *http.Request, token string) (auth.Info, time.Time, error)

// New return new auth.Strategy.
// The returned strategy, caches the invocation result of authenticate function, See AuthenticateFunc.
// Use NoOpAuthenticate to refresh/mangae token directly using cache or Append function, See NoOpAuthenticate.
// New return new token strategy that caches the invocation result of authenticate function.
func New(auth AuthenticateFunc, c auth.Cache, opts ...auth.Option) auth.Strategy {
if auth == nil {
panic("strategies/token: Authenticate Function required and can't be nil")
}

if c == nil {
panic("strategies/token: Cache object required and can't be nil")
}

cached := &cachedToken{
authFunc: auth,
cache: c,
Expand Down Expand Up @@ -56,11 +46,12 @@ func (c *cachedToken) Authenticate(ctx context.Context, r *http.Request) (auth.I

// if token not found invoke user authenticate function
if !ok {
info, err = c.authFunc(ctx, r, token)
var t time.Time
info, t, err = c.authFunc(ctx, r, token)
if err != nil {
return nil, err
}
c.cache.Store(token, info)
c.cache.StoreWithTTL(token, info, time.Until(t))
}

if _, ok := info.(auth.Info); !ok {
Expand All @@ -80,9 +71,9 @@ func (c *cachedToken) Revoke(token interface{}) error {
return nil
}

// NoOpAuthenticate implements Authenticate function, it return nil, ErrNOOP,
// NoOpAuthenticate implements AuthenticateFunc, it return nil, time.Time{}, ErrNOOP,
// commonly used when token refreshed/mangaed directly using cache or Append function,
// and there is no need to parse token and authenticate request.
func NoOpAuthenticate(ctx context.Context, r *http.Request, token string) (auth.Info, error) {
return nil, ErrNOOP
func NoOpAuthenticate(ctx context.Context, r *http.Request, token string) (auth.Info, time.Time, error) {
return nil, time.Time{}, ErrNOOP
}
33 changes: 6 additions & 27 deletions auth/strategies/token/cached_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"net/http"
"testing"
"time"

"github.com/shaj13/libcache"
_ "github.com/shaj13/libcache/lru"
Expand All @@ -15,7 +16,6 @@ import (
func TestNewCahced(t *testing.T) {
table := []struct {
name string
panic bool
expectedErr bool
authFunc AuthenticateFunc
info interface{}
Expand All @@ -25,49 +25,28 @@ func TestNewCahced(t *testing.T) {
name: "it return error when user authenticate func return error",
expectedErr: true,
authFunc: NoOpAuthenticate,
panic: false,
info: nil,
},
{
name: "it return error when cache return invalid type",
expectedErr: true,
authFunc: func(_ context.Context, _ *http.Request, _ string) (auth.Info, error) { return nil, nil },
panic: false,
info: "sample-data",
token: "valid",
authFunc: func(_ context.Context, _ *http.Request, _ string) (auth.Info, time.Time, error) {
return nil, time.Time{}, nil
},
info: "sample-data",
token: "valid",
},
{
name: "it return user when token cached",
expectedErr: false,
authFunc: NoOpAuthenticate,
panic: false,
info: auth.NewDefaultUser("1", "1", nil, nil),
token: "valid-user",
},
{
name: "it panic when Authenticate func nil",
expectedErr: false,
panic: true,
info: nil,
},
{
name: "it panic when Cache nil",
expectedErr: false,
authFunc: NoOpAuthenticate,
panic: true,
info: nil,
},
}

for _, tt := range table {
t.Run(tt.name, func(t *testing.T) {
if tt.panic {
assert.Panics(t, func() {
New(tt.authFunc, nil)
})
return
}

cache := libcache.LRU.New(0)
strategy := New(tt.authFunc, cache)
r, _ := http.NewRequest("GET", "/", nil)
Expand Down
13 changes: 7 additions & 6 deletions auth/strategies/token/example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"net/http"
"time"

"github.com/shaj13/libcache"
_ "github.com/shaj13/libcache/lru"
Expand Down Expand Up @@ -34,12 +35,12 @@ func ExampleNewStatic() {
}

func ExampleNew() {
authFunc := AuthenticateFunc(func(ctx context.Context, r *http.Request, token string) (auth.Info, error) {
authFunc := AuthenticateFunc(func(ctx context.Context, r *http.Request, token string) (auth.Info, time.Time, error) {
fmt.Print("authFunc called ")
if token == "90d64460d14870c08c81352a05dedd3465940a7" {
return auth.NewDefaultUser("example", "1", nil, nil), nil
return auth.NewDefaultUser("example", "1", nil, nil), time.Now().Add(time.Hour), nil
}
return nil, fmt.Errorf("Invalid user token")
return nil, time.Time{}, fmt.Errorf("Invalid user token")
})

cache := libcache.LRU.New(0)
Expand Down Expand Up @@ -138,11 +139,11 @@ func ExampleNew_apikey() {
parser := QueryParser("api_key")
opt := SetParser(parser)

authFunc := AuthenticateFunc(func(ctx context.Context, r *http.Request, token string) (auth.Info, error) {
authFunc := AuthenticateFunc(func(ctx context.Context, r *http.Request, token string) (auth.Info, time.Time, error) {
if token == "token" {
return auth.NewDefaultUser("example", "1", nil, nil), nil
return auth.NewDefaultUser("example", "1", nil, nil), time.Now().Add(time.Hour), nil
}
return nil, fmt.Errorf("Invalid user token")
return nil, time.Time{}, fmt.Errorf("Invalid user token")
})

cache := libcache.LRU.New(0)
Expand Down

0 comments on commit f509a3b

Please sign in to comment.