Skip to content
This repository has been archived by the owner on Jul 16, 2024. It is now read-only.

Commit

Permalink
Accept reva token as a bearer authentication (cs3org#3315)
Browse files Browse the repository at this point in the history
  • Loading branch information
gmgigi96 authored and vascoguita committed Oct 20, 2022
1 parent 0900e09 commit 25840a0
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 71 deletions.
3 changes: 3 additions & 0 deletions changelog/unreleased/reva-token-bearer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Enhancement: Accept reva token as a bearer authentication

https://github.com/cs3org/reva/pull/3315
174 changes: 103 additions & 71 deletions internal/http/interceptors/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,92 +200,87 @@ func authenticateUser(w http.ResponseWriter, r *http.Request, conf *config, toke
return nil, err
}

tkn := tokenStrategy.GetToken(r)
if tkn == "" {
log.Warn().Msg("core access token not set")
// reva token or auth token can be passed using the same tecnique (for example bearer)
// before validating it against an auth provider, we can check directly if it's a reva
// token and if not try to use it for authenticating the user.

token := tokenStrategy.GetToken(r)
if token != "" {
if user, ok := isTokenValid(r, tokenManager, token); ok {
if err := insertGroupsInUser(ctx, userGroupsCache, client, user); err != nil {
logError(isUnprotectedEndpoint, log, err, "got an error retrieving groups for user "+user.Username, http.StatusInternalServerError, w)
return nil, err
}
return ctxWithUserInfo(ctx, r, user, token), nil
}
}

userAgentCredKeys := getCredsForUserAgent(r.UserAgent(), conf.CredentialsByUserAgent, conf.CredentialChain)
log.Warn().Msg("core access token not set")

// obtain credentials (basic auth, bearer token, ...) based on user agent
var creds *auth.Credentials
for _, k := range userAgentCredKeys {
creds, err = credChain[k].GetCredentials(w, r)
if err != nil {
log.Debug().Err(err).Msg("error retrieving credentials")
}
userAgentCredKeys := getCredsForUserAgent(r.UserAgent(), conf.CredentialsByUserAgent, conf.CredentialChain)

if creds != nil {
log.Debug().Msgf("credentials obtained from credential strategy: type: %s, client_id: %s", creds.Type, creds.ClientID)
break
}
// obtain credentials (basic auth, bearer token, ...) based on user agent
var creds *auth.Credentials
for _, k := range userAgentCredKeys {
creds, err = credChain[k].GetCredentials(w, r)
if err != nil {
log.Debug().Err(err).Msg("error retrieving credentials")
}

// if no credentials are found, reply with authentication challenge depending on user agent
if creds == nil {
if !isUnprotectedEndpoint {
for _, key := range userAgentCredKeys {
if cred, ok := credChain[key]; ok {
cred.AddWWWAuthenticate(w, r, conf.Realm)
} else {
panic("auth credential strategy: " + key + "must have been loaded in init method")
}
if creds != nil {
log.Debug().Msgf("credentials obtained from credential strategy: type: %s, client_id: %s", creds.Type, creds.ClientID)
break
}
}

// if no credentials are found, reply with authentication challenge depending on user agent
if creds == nil {
if !isUnprotectedEndpoint {
for _, key := range userAgentCredKeys {
if cred, ok := credChain[key]; ok {
cred.AddWWWAuthenticate(w, r, conf.Realm)
} else {
panic("auth credential strategy: " + key + "must have been loaded in init method")
}
w.WriteHeader(http.StatusUnauthorized)
}
return nil, errtypes.PermissionDenied("no credentials found")
w.WriteHeader(http.StatusUnauthorized)
}
return nil, errtypes.PermissionDenied("no credentials found")
}

req := &gateway.AuthenticateRequest{
Type: creds.Type,
ClientId: creds.ClientID,
ClientSecret: creds.ClientSecret,
}
req := &gateway.AuthenticateRequest{
Type: creds.Type,
ClientId: creds.ClientID,
ClientSecret: creds.ClientSecret,
}

log.Debug().Msgf("AuthenticateRequest: type: %s, client_id: %s against %s", req.Type, req.ClientId, conf.GatewaySvc)
log.Debug().Msgf("AuthenticateRequest: type: %s, client_id: %s against %s", req.Type, req.ClientId, conf.GatewaySvc)

res, err := client.Authenticate(ctx, req)
if err != nil {
logError(isUnprotectedEndpoint, log, err, "error calling Authenticate", http.StatusUnauthorized, w)
return nil, err
}

if res.Status.Code != rpc.Code_CODE_OK {
err := status.NewErrorFromCode(res.Status.Code, "auth")
logError(isUnprotectedEndpoint, log, err, "error generating access token from credentials", http.StatusUnauthorized, w)
return nil, err
}
res, err := client.Authenticate(ctx, req)
if err != nil {
logError(isUnprotectedEndpoint, log, err, "error calling Authenticate", http.StatusUnauthorized, w)
return nil, err
}

log.Info().Msg("core access token generated")
// write token to response
tkn = res.Token
tokenWriter.WriteToken(tkn, w)
} else {
log.Debug().Msg("access token is already provided")
if res.Status.Code != rpc.Code_CODE_OK {
err := status.NewErrorFromCode(res.Status.Code, "auth")
logError(isUnprotectedEndpoint, log, err, "error generating access token from credentials", http.StatusUnauthorized, w)
return nil, err
}

log.Info().Msg("core access token generated")

// write token to response
token = res.Token
tokenWriter.WriteToken(token, w)

// validate token
u, tokenScope, err := tokenManager.DismantleToken(r.Context(), tkn)
u, tokenScope, err := tokenManager.DismantleToken(r.Context(), token)
if err != nil {
logError(isUnprotectedEndpoint, log, err, "error dismantling token", http.StatusUnauthorized, w)
return nil, err
}

if sharedconf.SkipUserGroupsInToken() {
var groups []string
if groupsIf, err := userGroupsCache.Get(u.Id.OpaqueId); err == nil {
groups = groupsIf.([]string)
} else {
groupsRes, err := client.GetUserGroups(ctx, &userpb.GetUserGroupsRequest{UserId: u.Id})
if err != nil {
logError(isUnprotectedEndpoint, log, err, "error retrieving user groups", http.StatusInternalServerError, w)
return nil, err
}
groups = groupsRes.Groups
_ = userGroupsCache.SetWithExpire(u.Id.OpaqueId, groupsRes.Groups, 3600*time.Second)
}
u.Groups = groups
}

// ensure access to the resource is allowed
ok, err := scope.VerifyScope(ctx, tokenScope, r.URL.Path)
if err != nil {
Expand All @@ -298,14 +293,51 @@ func authenticateUser(w http.ResponseWriter, r *http.Request, conf *config, toke
return nil, err
}

// store user and core access token in context.
ctx = ctxpkg.ContextSetUser(ctx, u)
ctx = ctxpkg.ContextSetToken(ctx, tkn)
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, tkn) // TODO(jfd): hardcoded metadata key. use PerRPCCredentials?
return ctxWithUserInfo(ctx, r, u, token), nil
}

func ctxWithUserInfo(ctx context.Context, r *http.Request, user *userpb.User, token string) context.Context {
ctx = ctxpkg.ContextSetUser(ctx, user)
ctx = ctxpkg.ContextSetToken(ctx, token)
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.TokenHeader, token)
ctx = metadata.AppendToOutgoingContext(ctx, ctxpkg.UserAgentHeader, r.UserAgent())

return ctx, nil
return ctx
}

func insertGroupsInUser(ctx context.Context, userGroupsCache gcache.Cache, client gateway.GatewayAPIClient, user *userpb.User) error {
if sharedconf.SkipUserGroupsInToken() {
var groups []string
if groupsIf, err := userGroupsCache.Get(user.Id.OpaqueId); err == nil {
groups = groupsIf.([]string)
} else {
groupsRes, err := client.GetUserGroups(ctx, &userpb.GetUserGroupsRequest{UserId: user.Id})
if err != nil {
return err
}
groups = groupsRes.Groups
_ = userGroupsCache.SetWithExpire(user.Id.OpaqueId, groupsRes.Groups, 3600*time.Second)
}
user.Groups = groups
}
return nil
}

func isTokenValid(r *http.Request, tokenManager token.Manager, token string) (*userpb.User, bool) {
ctx := r.Context()

u, tokenScope, err := tokenManager.DismantleToken(ctx, token)
if err != nil {
return nil, false
}

// ensure access to the resource is allowed
ok, err := scope.VerifyScope(ctx, tokenScope, r.URL.Path)
if err != nil {
return nil, false
}

return u, ok
}

func logError(isUnprotectedEndpoint bool, log *zerolog.Logger, err error, msg string, status int, w http.ResponseWriter) {
Expand Down
1 change: 1 addition & 0 deletions internal/http/interceptors/auth/token/loader/loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package loader

import (
// Load core token strategies.
_ "github.com/cs3org/reva/internal/http/interceptors/auth/token/strategy/bearer"
_ "github.com/cs3org/reva/internal/http/interceptors/auth/token/strategy/header"
// Add your own here.
)
84 changes: 84 additions & 0 deletions internal/http/interceptors/auth/token/strategy/bearer/bearer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

package header

import (
"mime"
"net/http"
"strings"

"github.com/cs3org/reva/internal/http/interceptors/auth/token/registry"
"github.com/cs3org/reva/pkg/auth"
)

func init() {
registry.Register("bearer", New)
}

type b struct{}

// New returns a new auth strategy that checks for bearer auth.
func New(m map[string]interface{}) (auth.TokenStrategy, error) {
return b{}, nil
}

func (b) GetToken(r *http.Request) string {
// Authorization Request Header Field: https://www.rfc-editor.org/rfc/rfc6750#section-2.1
if tkn, ok := getFromAuthorizationHeader(r); ok {
return tkn
}

// Form-Encoded Body Parameter: https://www.rfc-editor.org/rfc/rfc6750#section-2.2
if tkn, ok := getFromBody(r); ok {
return tkn
}

// URI Query Parameter: https://www.rfc-editor.org/rfc/rfc6750#section-2.3
if tkn, ok := getFromQueryParam(r); ok {
return tkn
}

return ""
}

func getFromAuthorizationHeader(r *http.Request) (string, bool) {
auth := r.Header.Get("Authorization")
tkn := strings.TrimPrefix(auth, "Bearer ")
return tkn, tkn != ""
}

func getFromBody(r *http.Request) (string, bool) {
mediatype, _, err := mime.ParseMediaType(r.Header.Get("content-type"))
if err != nil {
return "", false
}
if mediatype != "application/x-www-form-urlencoded" {
return "", false
}
if err = r.ParseForm(); err != nil {
return "", false
}
tkn := r.Form.Get("access-token")
return tkn, tkn != ""
}

func getFromQueryParam(r *http.Request) (string, bool) {
tkn := r.URL.Query().Get("access_token")
return tkn, tkn != ""
}

0 comments on commit 25840a0

Please sign in to comment.