mirror of
https://github.com/kataras/iris.git
synced 2025-02-02 15:30:36 +01:00
Add Context.SetUser and Context.User methods
relative to: https://github.com/iris-contrib/middleware/issues/63
This commit is contained in:
parent
dfe27567ae
commit
8e51a296b9
|
@ -28,6 +28,7 @@ The codebase for Dependency Injection, Internationalization and localization and
|
||||||
|
|
||||||
## Fixes and Improvements
|
## Fixes and Improvements
|
||||||
|
|
||||||
|
- A generic User interface, see the `Context.SetUser/User` methods in the New Context Methods section for more. In-short, the basicauth middleware's stored user can now be retrieved through `Context.User()` which provides more information than the native `ctx.Request().BasicAuth()` method one. Third-party authentication middleware creators can benefit of these two methods, plus the Logout below.
|
||||||
- A `Context.Logout` method is added, can be used to invalidate [basicauth](https://github.com/kataras/iris/blob/master/_examples/auth/basicauth/main.go) client credentials.
|
- A `Context.Logout` method is added, can be used to invalidate [basicauth](https://github.com/kataras/iris/blob/master/_examples/auth/basicauth/main.go) client credentials.
|
||||||
- Add the ability to [share functions](https://github.com/kataras/iris/tree/master/_examples/routing/writing-a-middleware/share-funcs) between handlers chain and add an [example](https://github.com/kataras/iris/tree/master/_examples/routing/writing-a-middleware/share-services) on sharing Go structures (aka services).
|
- Add the ability to [share functions](https://github.com/kataras/iris/tree/master/_examples/routing/writing-a-middleware/share-funcs) between handlers chain and add an [example](https://github.com/kataras/iris/tree/master/_examples/routing/writing-a-middleware/share-services) on sharing Go structures (aka services).
|
||||||
|
|
||||||
|
@ -309,6 +310,7 @@ var dirOpts = iris.DirOptions{
|
||||||
|
|
||||||
## New Context Methods
|
## New Context Methods
|
||||||
|
|
||||||
|
- `Context.SetUser(User)` and `Context.User() User` to store and retrieve an authenticated client. Read more [here](https://github.com/iris-contrib/middleware/issues/63).
|
||||||
- `Context.SetLogoutFunc(fn interface{}, persistenceArgs ...interface{})` and `Logout(args ...interface{}) error` methods to allow different kind of auth middlewares to be able to set a "logout" a user/client feature with a single function, the route handler may not be aware of the implementation of the authentication used.
|
- `Context.SetLogoutFunc(fn interface{}, persistenceArgs ...interface{})` and `Logout(args ...interface{}) error` methods to allow different kind of auth middlewares to be able to set a "logout" a user/client feature with a single function, the route handler may not be aware of the implementation of the authentication used.
|
||||||
- `Context.SetFunc(name string, fn interface{}, persistenceArgs ...interface{})` and `Context.CallFunc(name string, args ...interface{}) ([]reflect.Value, error)` to allow middlewares to share functions dynamically when the type of the function is not predictable, see the [example](https://github.com/kataras/iris/tree/master/_examples/routing/writing-a-middleware/share-funcs) for more.
|
- `Context.SetFunc(name string, fn interface{}, persistenceArgs ...interface{})` and `Context.CallFunc(name string, args ...interface{}) ([]reflect.Value, error)` to allow middlewares to share functions dynamically when the type of the function is not predictable, see the [example](https://github.com/kataras/iris/tree/master/_examples/routing/writing-a-middleware/share-funcs) for more.
|
||||||
- `Context.TextYAML(interface{}) error` same as `Context.YAML` but with set the Content-Type to `text/yaml` instead (Google Chrome renders it as text).
|
- `Context.TextYAML(interface{}) error` same as `Context.YAML` but with set the Content-Type to `text/yaml` instead (Google Chrome renders it as text).
|
||||||
|
|
|
@ -51,11 +51,12 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func h(ctx iris.Context) {
|
func h(ctx iris.Context) {
|
||||||
username, password, _ := ctx.Request().BasicAuth()
|
// username, password, _ := ctx.Request().BasicAuth()
|
||||||
// third parameter it will be always true because the middleware
|
// third parameter it will be always true because the middleware
|
||||||
// makes sure for that, otherwise this handler will not be executed.
|
// makes sure for that, otherwise this handler will not be executed.
|
||||||
|
// OR:
|
||||||
ctx.Writef("%s %s:%s", ctx.Path(), username, password)
|
user := ctx.User()
|
||||||
|
ctx.Writef("%s %s:%s", ctx.Path(), user.GetUsername(), user.GetPassword())
|
||||||
}
|
}
|
||||||
|
|
||||||
func logout(ctx iris.Context) {
|
func logout(ctx iris.Context) {
|
||||||
|
|
|
@ -2133,12 +2133,12 @@ const disableRequestBodyConsumptionContextKey = "iris.request.body.record"
|
||||||
// but acts for the current request.
|
// but acts for the current request.
|
||||||
// It makes the request body readable more than once.
|
// It makes the request body readable more than once.
|
||||||
func (ctx *Context) RecordBody() {
|
func (ctx *Context) RecordBody() {
|
||||||
ctx.Values().Set(disableRequestBodyConsumptionContextKey, true)
|
ctx.values.Set(disableRequestBodyConsumptionContextKey, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRecordingBody reports whether the request body can be readen multiple times.
|
// IsRecordingBody reports whether the request body can be readen multiple times.
|
||||||
func (ctx *Context) IsRecordingBody() bool {
|
func (ctx *Context) IsRecordingBody() bool {
|
||||||
return ctx.Values().GetBoolDefault(disableRequestBodyConsumptionContextKey,
|
return ctx.values.GetBoolDefault(disableRequestBodyConsumptionContextKey,
|
||||||
ctx.app.ConfigurationReadOnly().GetDisableBodyConsumptionOnUnmarshal())
|
ctx.app.ConfigurationReadOnly().GetDisableBodyConsumptionOnUnmarshal())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5253,6 +5253,28 @@ func (ctx *Context) Logout(args ...interface{}) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userContextKey = "iris.user"
|
||||||
|
|
||||||
|
// SetUser sets a User for this request.
|
||||||
|
// It's used by auth middlewares as a common
|
||||||
|
// method to provide user information to the
|
||||||
|
// next handlers in the chain.
|
||||||
|
func (ctx *Context) SetUser(u User) {
|
||||||
|
ctx.values.Set(userContextKey, u)
|
||||||
|
}
|
||||||
|
|
||||||
|
// User returns the registered User of this request.
|
||||||
|
// See `SetUser` too.
|
||||||
|
func (ctx *Context) User() User {
|
||||||
|
if v := ctx.values.Get(userContextKey); v != nil {
|
||||||
|
if u, ok := v.(User); ok {
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
const idContextKey = "iris.context.id"
|
const idContextKey = "iris.context.id"
|
||||||
|
|
||||||
// SetID sets an ID, any value, to the Request Context.
|
// SetID sets an ID, any value, to the Request Context.
|
||||||
|
|
144
context/context_user.go
Normal file
144
context/context_user.go
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
package context
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrNotSupported is fired when a specific method is not implemented
|
||||||
|
// or not supported entirely.
|
||||||
|
// Can be used by User implementations when
|
||||||
|
// an authentication system does not implement a specific, but required,
|
||||||
|
// method of the User interface.
|
||||||
|
var ErrNotSupported = errors.New("not supported")
|
||||||
|
|
||||||
|
// User is a generic view of an authorized client.
|
||||||
|
// See `Context.User` and `SetUser` methods for more.
|
||||||
|
//
|
||||||
|
// The informational methods starts with a "Get" prefix
|
||||||
|
// in order to allow the implementation to contain exported
|
||||||
|
// fields such as `Username` so they can be JSON encoded when necessary.
|
||||||
|
//
|
||||||
|
// The caller is free to cast this with the implementation directly
|
||||||
|
// when special features are offered by the authorization system.
|
||||||
|
type User interface {
|
||||||
|
// GetAuthorization should return the authorization method,
|
||||||
|
// e.g. Basic Authentication.
|
||||||
|
GetAuthorization() string
|
||||||
|
// GetAuthorizedAt should return the exact time the
|
||||||
|
// client has been authorized for the "first" time.
|
||||||
|
GetAuthorizedAt() time.Time
|
||||||
|
// GetUsername should return the name of the User.
|
||||||
|
GetUsername() string
|
||||||
|
// GetPassword should return the encoded or raw password
|
||||||
|
// (depends on the implementation) of the User.
|
||||||
|
GetPassword() string
|
||||||
|
// GetEmail should return the e-mail of the User.
|
||||||
|
GetEmail() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// FeaturedUser optional interface that a User can implement.
|
||||||
|
type FeaturedUser interface {
|
||||||
|
User
|
||||||
|
// GetFeatures should optionally return a list of features
|
||||||
|
// the User implementation offers.
|
||||||
|
GetFeatures() []UserFeature
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserFeature a type which represents a user's optional feature.
|
||||||
|
// See `HasUserFeature` function for more.
|
||||||
|
type UserFeature uint32
|
||||||
|
|
||||||
|
// The list of standard UserFeatures.
|
||||||
|
const (
|
||||||
|
AuthorizedAtFeature UserFeature = iota
|
||||||
|
UsernameFeature
|
||||||
|
PasswordFeature
|
||||||
|
EmailFeature
|
||||||
|
)
|
||||||
|
|
||||||
|
// HasUserFeature reports whether the "u" User
|
||||||
|
// implements a specific "feature" User Feature.
|
||||||
|
//
|
||||||
|
// It returns ErrNotSupported if a user does not implement
|
||||||
|
// the FeaturedUser interface.
|
||||||
|
func HasUserFeature(user User, feature UserFeature) (bool, error) {
|
||||||
|
if u, ok := user.(FeaturedUser); ok {
|
||||||
|
for _, f := range u.GetFeatures() {
|
||||||
|
if f == feature {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleUser is a simple implementation of the User interface.
|
||||||
|
type SimpleUser struct {
|
||||||
|
Authorization string `json:"authorization"`
|
||||||
|
AuthorizedAt time.Time `json:"authorized_at"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"-"`
|
||||||
|
Email string `json:"email,omitempty"`
|
||||||
|
Features []UserFeature `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ User = (*SimpleUser)(nil)
|
||||||
|
|
||||||
|
// GetAuthorization returns the authorization method,
|
||||||
|
// e.g. Basic Authentication.
|
||||||
|
func (u *SimpleUser) GetAuthorization() string {
|
||||||
|
return u.Authorization
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAuthorizedAt returns the exact time the
|
||||||
|
// client has been authorized for the "first" time.
|
||||||
|
func (u *SimpleUser) GetAuthorizedAt() time.Time {
|
||||||
|
return u.AuthorizedAt
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUsername returns the name of the User.
|
||||||
|
func (u *SimpleUser) GetUsername() string {
|
||||||
|
return u.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPassword returns the raw password of the User.
|
||||||
|
func (u *SimpleUser) GetPassword() string {
|
||||||
|
return u.Password
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEmail returns the e-mail of the User.
|
||||||
|
func (u *SimpleUser) GetEmail() string {
|
||||||
|
return u.Email
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFeatures returns a list of features
|
||||||
|
// this User implementation offers.
|
||||||
|
func (u *SimpleUser) GetFeatures() []UserFeature {
|
||||||
|
if u.Features != nil {
|
||||||
|
return u.Features
|
||||||
|
}
|
||||||
|
|
||||||
|
var features []UserFeature
|
||||||
|
|
||||||
|
if !u.AuthorizedAt.IsZero() {
|
||||||
|
features = append(features, AuthorizedAtFeature)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Username != "" {
|
||||||
|
features = append(features, UsernameFeature)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Password != "" {
|
||||||
|
features = append(features, PasswordFeature)
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Email != "" {
|
||||||
|
features = append(features, EmailFeature)
|
||||||
|
}
|
||||||
|
|
||||||
|
return features
|
||||||
|
}
|
|
@ -1,7 +1,11 @@
|
||||||
// Package basicauth provides http basic authentication via middleware. See _examples/auth/basicauth
|
// Package basicauth provides http basic authentication via middleware. See _examples/auth/basicauth
|
||||||
package basicauth
|
package basicauth
|
||||||
|
|
||||||
// test file: ../../_examples/auth/basicauth/main_test.go
|
/*
|
||||||
|
Test files:
|
||||||
|
- ../../_examples/auth/basicauth/main_test.go
|
||||||
|
- ./basicauth_test.go
|
||||||
|
*/
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
@ -16,14 +20,18 @@ func init() {
|
||||||
context.SetHandlerName("iris/middleware/basicauth.*", "iris.basicauth")
|
context.SetHandlerName("iris/middleware/basicauth.*", "iris.basicauth")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const authorizationType = "Basic Authentication"
|
||||||
|
|
||||||
type (
|
type (
|
||||||
encodedUser struct {
|
encodedUser struct {
|
||||||
HeaderValue string
|
HeaderValue string
|
||||||
Username string
|
Username string
|
||||||
logged bool
|
Password string
|
||||||
forceLogout bool // in order to be able to invalidate and use a redirect response.
|
logged bool
|
||||||
expires time.Time
|
forceLogout bool // in order to be able to invalidate and use a redirect response.
|
||||||
mu sync.RWMutex
|
authorizedAt time.Time // when from !logged to logged.
|
||||||
|
expires time.Time
|
||||||
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
basicAuthMiddleware struct {
|
basicAuthMiddleware struct {
|
||||||
|
@ -45,6 +53,8 @@ type (
|
||||||
// which will ask the client for basic auth (username, password),
|
// which will ask the client for basic auth (username, password),
|
||||||
// validate that and if valid continues to the next handler, otherwise
|
// validate that and if valid continues to the next handler, otherwise
|
||||||
// throws a StatusUnauthorized http error code.
|
// throws a StatusUnauthorized http error code.
|
||||||
|
//
|
||||||
|
// Use the `Context.User` method to retrieve the stored user.
|
||||||
func New(c Config) context.Handler {
|
func New(c Config) context.Handler {
|
||||||
config := DefaultConfig()
|
config := DefaultConfig()
|
||||||
if c.Realm != "" {
|
if c.Realm != "" {
|
||||||
|
@ -76,7 +86,13 @@ func (b *basicAuthMiddleware) init() {
|
||||||
for k, v := range b.config.Users {
|
for k, v := range b.config.Users {
|
||||||
fullUser := k + ":" + v
|
fullUser := k + ":" + v
|
||||||
header := "Basic " + base64.StdEncoding.EncodeToString([]byte(fullUser))
|
header := "Basic " + base64.StdEncoding.EncodeToString([]byte(fullUser))
|
||||||
b.auth = append(b.auth, &encodedUser{HeaderValue: header, Username: k, logged: false, expires: DefaultExpireTime})
|
b.auth = append(b.auth, &encodedUser{
|
||||||
|
HeaderValue: header,
|
||||||
|
Username: k,
|
||||||
|
Password: v,
|
||||||
|
logged: false,
|
||||||
|
expires: DefaultExpireTime,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the auth realm header's value
|
// set the auth realm header's value
|
||||||
|
@ -106,7 +122,8 @@ func (b *basicAuthMiddleware) askForCredentials(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serve the actual middleware
|
// Serve the actual basic authentication middleware.
|
||||||
|
// Use the Context.User method to retrieve the stored user.
|
||||||
func (b *basicAuthMiddleware) Serve(ctx *context.Context) {
|
func (b *basicAuthMiddleware) Serve(ctx *context.Context) {
|
||||||
auth, found := b.findAuth(ctx.GetHeader("Authorization"))
|
auth, found := b.findAuth(ctx.GetHeader("Authorization"))
|
||||||
if !found || auth.forceLogout {
|
if !found || auth.forceLogout {
|
||||||
|
@ -122,11 +139,20 @@ func (b *basicAuthMiddleware) Serve(ctx *context.Context) {
|
||||||
// don't continue to the next handler
|
// don't continue to the next handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auth.mu.RLock()
|
||||||
|
logged := auth.logged
|
||||||
|
auth.mu.RUnlock()
|
||||||
|
if !logged {
|
||||||
|
auth.mu.Lock()
|
||||||
|
auth.authorizedAt = time.Now()
|
||||||
|
auth.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// all ok
|
// all ok
|
||||||
if b.expireEnabled {
|
if b.expireEnabled {
|
||||||
if !auth.logged {
|
if !logged {
|
||||||
auth.mu.Lock()
|
auth.mu.Lock()
|
||||||
auth.expires = time.Now().Add(b.config.Expires)
|
auth.expires = auth.authorizedAt.Add(b.config.Expires)
|
||||||
auth.logged = true
|
auth.logged = true
|
||||||
auth.mu.Unlock()
|
auth.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
@ -137,6 +163,7 @@ func (b *basicAuthMiddleware) Serve(ctx *context.Context) {
|
||||||
if expired {
|
if expired {
|
||||||
auth.mu.Lock()
|
auth.mu.Lock()
|
||||||
auth.logged = false
|
auth.logged = false
|
||||||
|
auth.forceLogout = false
|
||||||
auth.mu.Unlock()
|
auth.mu.Unlock()
|
||||||
b.askForCredentials(ctx) // ask for authentication again
|
b.askForCredentials(ctx) // ask for authentication again
|
||||||
ctx.StopExecution()
|
ctx.StopExecution()
|
||||||
|
@ -144,8 +171,18 @@ func (b *basicAuthMiddleware) Serve(ctx *context.Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !b.config.DisableLogoutFunc {
|
if !b.config.DisableContextUser {
|
||||||
ctx.SetLogoutFunc(b.Logout)
|
ctx.SetLogoutFunc(b.Logout)
|
||||||
|
|
||||||
|
auth.mu.RLock()
|
||||||
|
user := &context.SimpleUser{
|
||||||
|
Authorization: authorizationType,
|
||||||
|
AuthorizedAt: auth.authorizedAt,
|
||||||
|
Username: auth.Username,
|
||||||
|
Password: auth.Password,
|
||||||
|
}
|
||||||
|
auth.mu.RUnlock()
|
||||||
|
ctx.SetUser(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.Next() // continue
|
ctx.Next() // continue
|
||||||
|
|
|
@ -20,6 +20,15 @@ func TestBasicAuthUseRouter(t *testing.T) {
|
||||||
|
|
||||||
app.UseRouter(basicauth.Default(users))
|
app.UseRouter(basicauth.Default(users))
|
||||||
|
|
||||||
|
app.Get("/user_json", func(ctx iris.Context) {
|
||||||
|
ctx.JSON(ctx.User())
|
||||||
|
})
|
||||||
|
|
||||||
|
app.Get("/user_string", func(ctx iris.Context) {
|
||||||
|
user := ctx.User()
|
||||||
|
ctx.Writef("%s\n%s\n%s", user.GetAuthorization(), user.GetUsername(), user.GetPassword())
|
||||||
|
})
|
||||||
|
|
||||||
app.Get("/", func(ctx iris.Context) {
|
app.Get("/", func(ctx iris.Context) {
|
||||||
username, _, _ := ctx.Request().BasicAuth()
|
username, _, _ := ctx.Request().BasicAuth()
|
||||||
ctx.Writef("Hello, %s!", username)
|
ctx.Writef("Hello, %s!", username)
|
||||||
|
@ -55,6 +64,13 @@ func TestBasicAuthUseRouter(t *testing.T) {
|
||||||
// Test pass authentication and route found.
|
// Test pass authentication and route found.
|
||||||
e.GET("/").WithBasicAuth(username, password).Expect().
|
e.GET("/").WithBasicAuth(username, password).Expect().
|
||||||
Status(httptest.StatusOK).Body().Equal(fmt.Sprintf("Hello, %s!", username))
|
Status(httptest.StatusOK).Body().Equal(fmt.Sprintf("Hello, %s!", username))
|
||||||
|
e.GET("/user_json").WithBasicAuth(username, password).Expect().
|
||||||
|
Status(httptest.StatusOK).JSON().Object().ContainsMap(iris.Map{
|
||||||
|
"username": username,
|
||||||
|
})
|
||||||
|
e.GET("/user_string").WithBasicAuth(username, password).Expect().
|
||||||
|
Status(httptest.StatusOK).Body().
|
||||||
|
Equal(fmt.Sprintf("%s\n%s\n%s", "Basic Authentication", username, password))
|
||||||
|
|
||||||
// Test empty auth.
|
// Test empty auth.
|
||||||
e.GET("/").Expect().Status(httptest.StatusUnauthorized).Body().Equal("Unauthorized")
|
e.GET("/").Expect().Status(httptest.StatusUnauthorized).Body().Equal("Unauthorized")
|
||||||
|
|
|
@ -43,8 +43,9 @@ type Config struct {
|
||||||
// Defaults to nil.
|
// Defaults to nil.
|
||||||
OnAsk context.Handler
|
OnAsk context.Handler
|
||||||
|
|
||||||
// DisableLogoutFunc disables the registration of the custom basicauth Context.Logout.
|
// DisableContextUser disables the registration of the custom basicauth Context.Logout
|
||||||
DisableLogoutFunc bool
|
// and the User.
|
||||||
|
DisableContextUser bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultConfig returns the default configs for the BasicAuth middleware
|
// DefaultConfig returns the default configs for the BasicAuth middleware
|
||||||
|
|
Loading…
Reference in New Issue
Block a user