// HasRole returns an AuthCheck that grants access under the following conditions: // - The account specified by the token has the specified role. // - The token itself has that role in scope. // - The token is not used up. func HasRole(role string) AuthCheck { return func(ctx context.Context, w http.ResponseWriter, r *http.Request) error { var acct account.Account var claimSet jws.ClaimSet if err := GetAccount(ctx, &acct); err != nil { // we can't get the user, so we can't check authentication status. log.Errorf(ctx, "Error getting account for authentication: %s", err.Error()) return ErrCannotGetAccount } else if err := GetClaimSet(ctx, &claimSet); err != nil { log.Errorf(ctx, "Error getting claim set for authentication: %s", err.Error()) return ErrCannotGetClaimSet } else if !acct.HasRole(role) { // the account making the request does not have the specified role. return ErrRoleMissing } else if !roleInScope(ctx, role) { // the JWT claimset for this request does not have the specified role in its scope. return ErrRoleNotInScope } else if err = UseClaimSet(ctx, &claimSet); err == ErrClaimSetUsedUp { // The claimset for this request has been all used up. return err } else if err != nil { return errors.New(http.StatusBadRequest, err.Error()) } else { // All ok; this request's account may access the resource. return nil } } }
func getJwt(ctx context.Context, w http.ResponseWriter, r *http.Request) { var acct account.Account var email string var err error conf := config.Global{} if err := config.Get(ctx, &conf); err != nil { rest.WriteJSON(w, err) return } // read the account ID if emailBytes, err := base64.RawURLEncoding.DecodeString(rest.Param(ctx, "id")); err != nil { rest.WriteJSON(w, err) return } else { email = string(emailBytes) } if err = account.Get(ctx, email, &acct); err != nil { rest.WriteJSON(w, err) return } // All set. Generate the JWT. jwt, err := auth.Encode(&jws.ClaimSet{ Sub: acct.Email, Scope: strings.Join(acct.Roles, ","), Exp: time.Now().AddDate(1, 0, 0).Unix(), PrivateClaims: map[string]interface{}{ "n": "DEV USER", }, }, []byte(conf.AuthSecret)) if err != nil { rest.WriteJSON(w, errors.New(http.StatusInternalServerError, "Could not generate JWT")) return } else { jwtString := string(jwt) rest.WriteJSON(w, &jwtString) return } }
package config import ( "encoding/json" "github.com/qedus/nds" "github.com/the-information/ori/errors" "github.com/the-information/ori/internal" "golang.org/x/net/context" "google.golang.org/appengine/datastore" "net/http" ) var ErrNotInConfigContext = errors.New(http.StatusInternalServerError, "That context was not run through the ori/config middleware") var ErrConflict = errors.New(http.StatusConflict, "There was a conflict between versions of the object being saved") // Entity is the string name for the Entity used to store the configuration // in the App Engine Datastore. Think of it like a table name. const Entity = "Config" // Global describes some configuration parameters that are required for the API to function. type Global struct { // AuthSecret is the secret key by which all JWTs are signed using a SHA-256 HMAC. AuthSecret string `json:",omitempty"` // ValidOriginSuffix is the suffix for which CORS requests are valid for this app. ValidOriginSuffix string `json:",omitempty"` } // Config is a type that can represent the full state of the application at any time. // It's quite slow because it has to rely on reflection. Use config.Get to pull config // into your own struct instead. type Config datastore.PropertyList
"golang.org/x/net/context" "google.golang.org/appengine/datastore" "net/http" "reflect" "sync" "time" ) var xgTransaction = &datastore.TransactionOptions{ XG: true, } // Entity is the name of the Datastore entity used to store API accounts. var Entity = "APIAccount" var ErrConflict = errors.New(http.StatusConflict, "A competing change to the account has already been made") var ErrAccountExists = errors.New(http.StatusConflict, "An account with that email already exists") var ErrPasswordTooShort = errors.New(http.StatusBadRequest, "Password is too short") var ErrUnsaveableAccount = errors.New(http.StatusBadRequest, "This is a special account that cannot be saved") // Account represents an account to access the API. It handles // all logic to do with authentication and password checking. type Account struct { flag int // CreatedAt stores the time at which this account was originally created. CreatedAt time.Time `json:"createdAt,omitempty"` // LastUpdatedAt represents the last time at which this account was modified. LastUpdatedAt time.Time `json:"lastUpdatedAt,omitempty"`
"github.com/guregu/kami" "github.com/the-information/ori/account" "github.com/the-information/ori/config" "github.com/the-information/ori/errors" "github.com/the-information/ori/internal" "github.com/the-information/ori/rest" "github.com/the-information/ori/shard" "golang.org/x/net/context" "golang.org/x/oauth2/jws" "google.golang.org/appengine/log" "net/http" "strings" ) var ( ErrForbidden = errors.New(http.StatusForbidden, "The account does not have permission to read the specified resource") ErrCannotGetAccount = errors.New(http.StatusUnauthorized, "There was an error retrieving the account to be authenticated. Please try again.") ErrCannotGetClaimSet = errors.New(http.StatusUnauthorized, "There was an error retrieving the claim set to be authenticated. Please try again.") ErrRoleMissing = errors.New(http.StatusForbidden, "The specified account does not have the specified role") ErrRoleNotInScope = errors.New(http.StatusUnauthorized, "The authentication token does not have the specified role in scope") ErrNotInAuthContext = errors.New(http.StatusInternalServerError, "That context object was not run through auth.Middleware!") ErrAccountIDDoesNotMatch = errors.New(http.StatusForbidden, "Account ID does not match route parameter") ErrInvalidConsumableClaimSet = errors.New(http.StatusForbidden, "Claimset has a u claim but not a jti claim") ErrClaimSetUsedUp = errors.New(http.StatusUnauthorized, "Claimset has been used up") ) // Middleware sets up the request context so account information can be // retrieved with auth.GetAccount(ctx). It panics if config.Get(ctx) fails. func Middleware(ctx context.Context, w http.ResponseWriter, r *http.Request) context.Context { var conf config.Global