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
|
||||
|
||||
- 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.
|
||||
- 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
|
||||
|
||||
- `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.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).
|
||||
|
|
|
@ -51,11 +51,12 @@ func main() {
|
|||
}
|
||||
|
||||
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
|
||||
// makes sure for that, otherwise this handler will not be executed.
|
||||
|
||||
ctx.Writef("%s %s:%s", ctx.Path(), username, password)
|
||||
// OR:
|
||||
user := ctx.User()
|
||||
ctx.Writef("%s %s:%s", ctx.Path(), user.GetUsername(), user.GetPassword())
|
||||
}
|
||||
|
||||
func logout(ctx iris.Context) {
|
||||
|
|
|
@ -2133,12 +2133,12 @@ const disableRequestBodyConsumptionContextKey = "iris.request.body.record"
|
|||
// but acts for the current request.
|
||||
// It makes the request body readable more than once.
|
||||
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.
|
||||
func (ctx *Context) IsRecordingBody() bool {
|
||||
return ctx.Values().GetBoolDefault(disableRequestBodyConsumptionContextKey,
|
||||
return ctx.values.GetBoolDefault(disableRequestBodyConsumptionContextKey,
|
||||
ctx.app.ConfigurationReadOnly().GetDisableBodyConsumptionOnUnmarshal())
|
||||
}
|
||||
|
||||
|
@ -5253,6 +5253,28 @@ func (ctx *Context) Logout(args ...interface{}) error {
|
|||
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"
|
||||
|
||||
// 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
|
||||
|
||||
// test file: ../../_examples/auth/basicauth/main_test.go
|
||||
/*
|
||||
Test files:
|
||||
- ../../_examples/auth/basicauth/main_test.go
|
||||
- ./basicauth_test.go
|
||||
*/
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
|
@ -16,12 +20,16 @@ func init() {
|
|||
context.SetHandlerName("iris/middleware/basicauth.*", "iris.basicauth")
|
||||
}
|
||||
|
||||
const authorizationType = "Basic Authentication"
|
||||
|
||||
type (
|
||||
encodedUser struct {
|
||||
HeaderValue string
|
||||
Username string
|
||||
Password string
|
||||
logged bool
|
||||
forceLogout bool // in order to be able to invalidate and use a redirect response.
|
||||
authorizedAt time.Time // when from !logged to logged.
|
||||
expires time.Time
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
@ -45,6 +53,8 @@ type (
|
|||
// which will ask the client for basic auth (username, password),
|
||||
// validate that and if valid continues to the next handler, otherwise
|
||||
// throws a StatusUnauthorized http error code.
|
||||
//
|
||||
// Use the `Context.User` method to retrieve the stored user.
|
||||
func New(c Config) context.Handler {
|
||||
config := DefaultConfig()
|
||||
if c.Realm != "" {
|
||||
|
@ -76,7 +86,13 @@ func (b *basicAuthMiddleware) init() {
|
|||
for k, v := range b.config.Users {
|
||||
fullUser := k + ":" + v
|
||||
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
|
||||
|
@ -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) {
|
||||
auth, found := b.findAuth(ctx.GetHeader("Authorization"))
|
||||
if !found || auth.forceLogout {
|
||||
|
@ -122,11 +139,20 @@ func (b *basicAuthMiddleware) Serve(ctx *context.Context) {
|
|||
// 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
|
||||
if b.expireEnabled {
|
||||
if !auth.logged {
|
||||
if !logged {
|
||||
auth.mu.Lock()
|
||||
auth.expires = time.Now().Add(b.config.Expires)
|
||||
auth.expires = auth.authorizedAt.Add(b.config.Expires)
|
||||
auth.logged = true
|
||||
auth.mu.Unlock()
|
||||
}
|
||||
|
@ -137,6 +163,7 @@ func (b *basicAuthMiddleware) Serve(ctx *context.Context) {
|
|||
if expired {
|
||||
auth.mu.Lock()
|
||||
auth.logged = false
|
||||
auth.forceLogout = false
|
||||
auth.mu.Unlock()
|
||||
b.askForCredentials(ctx) // ask for authentication again
|
||||
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)
|
||||
|
||||
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
|
||||
|
|
|
@ -20,6 +20,15 @@ func TestBasicAuthUseRouter(t *testing.T) {
|
|||
|
||||
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) {
|
||||
username, _, _ := ctx.Request().BasicAuth()
|
||||
ctx.Writef("Hello, %s!", username)
|
||||
|
@ -55,6 +64,13 @@ func TestBasicAuthUseRouter(t *testing.T) {
|
|||
// Test pass authentication and route found.
|
||||
e.GET("/").WithBasicAuth(username, password).Expect().
|
||||
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.
|
||||
e.GET("/").Expect().Status(httptest.StatusUnauthorized).Body().Equal("Unauthorized")
|
||||
|
|
|
@ -43,8 +43,9 @@ type Config struct {
|
|||
// Defaults to nil.
|
||||
OnAsk context.Handler
|
||||
|
||||
// DisableLogoutFunc disables the registration of the custom basicauth Context.Logout.
|
||||
DisableLogoutFunc bool
|
||||
// DisableContextUser disables the registration of the custom basicauth Context.Logout
|
||||
// and the User.
|
||||
DisableContextUser bool
|
||||
}
|
||||
|
||||
// DefaultConfig returns the default configs for the BasicAuth middleware
|
||||
|
|
Loading…
Reference in New Issue
Block a user