Skip to content
This repository has been archived by the owner on Mar 11, 2021. It is now read-only.

Commit

Permalink
Audit API for validating/updating RPT tokens (#561)
Browse files Browse the repository at this point in the history
Fixes #544, Fixes #611

Implements an endpoint which allows a token to be audited in respect to the user's privileges for a specified resource ID.  Returns a new RPT token if the current token does not contain the required privileges, or the privileges have expired.  Otherwise returns an empty response if the presented token already contains the up-to-date state of the user's privileges for the resource.

Request:

```
Authorization: Bearer $ACCESS_TOKEN_OR_RPT
POST /api/token/audit?resource_id=c0ee2b94-aee3-4c41-9e15-6fa330ce8e0b
```

Response when a new RPT token has been issued:

```
{
  rpt_token: eyJhbGciOiJ____token___GeFIyvT_sIDyPgYFSR2YCN4_N3CSQPfQYdrQhDGKM7fKLBKnYqAwfUe2OeibQ
}
```

Decoded RPT token:

```
{ 
  alg: "RS256", 
  kid: "aUGv8mQA85jg4V1DU8Uk1W0uKsxn187KQONAGl6AMtc", 
  typ: "JWT"
}.
{ 
  acr: "0", 
  allowed-origins:[ 
   "http://auth.openshift.io",  "http://openshift.io" 
  ], 
  approved: true, 
  aud: "http://openshift.io", 
  auth_time: 1535414160, 
  azp: "http://openshift.io", 
  email: "[email protected]", 
  email_verified: false, 
  exp: 1538006160, 
  family_name: "", 
  given_name: "TestUser-50edff18-6c86-4910-b069-37d68f1c02c1", 
  iat: 1535414160, 
  iss: "http://auth.openshift.io", 
  jti: "109d09ed-91cc-4393-8fa1-bc3187aa40ba", 
  name: "TestUser-50edff18-6c86-4910-b069-37d68f1c02c1", 
  nbf: 0, 
  permissions: [  
    {
      resource_set_name: null,
      resource_set_id: "c0ee2b94-aee3-4c41-9e15-6fa330ce8e0b",
      scopes: ["lima"],
      exp: 1535500572
    } 
  ], 
  preferred_username: "TestUserIdentity-50edff18-6c86-4910-b069-37d68f1c02c1", 
  realm_access: {  
    roles: [   "uma_authorization"  ] 
  }, 
  resource_access: {  
    account: {   
      roles: [    "manage-account",    "manage-account-links",    "view-profile"   ]  
    },  
    broker: {   roles: [    "read-token"   ]  } 
  }, 
  session_state: "", 
  sub: "7aca58df-b6e1-4a58-8d3a-600df382dd40", 
  typ: "Bearer"
}.
[signature]
--

```
  • Loading branch information
sbryzak authored and sbose78 committed Aug 29, 2018
1 parent f261844 commit 62cc9ff
Show file tree
Hide file tree
Showing 40 changed files with 2,588 additions and 182 deletions.
2 changes: 2 additions & 0 deletions application/repository/repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
account "github.com/fabric8-services/fabric8-auth/account/repository"
"github.com/fabric8-services/fabric8-auth/auth"
invitation "github.com/fabric8-services/fabric8-auth/authorization/invitation/repository"
permission "github.com/fabric8-services/fabric8-auth/authorization/permission/repository"
resource "github.com/fabric8-services/fabric8-auth/authorization/resource/repository"
resourcetype "github.com/fabric8-services/fabric8-auth/authorization/resourcetype/repository"
role "github.com/fabric8-services/fabric8-auth/authorization/role/repository"
Expand All @@ -27,4 +28,5 @@ type Repositories interface {
DefaultRoleMappingRepository() role.DefaultRoleMappingRepository
RoleMappingRepository() role.RoleMappingRepository
TokenRepository() token.TokenRepository
PrivilegeCacheRepository() permission.PrivilegeCacheRepository
}
25 changes: 17 additions & 8 deletions application/service/factory/service_factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
roleservice "github.com/fabric8-services/fabric8-auth/authorization/role/service"
spaceservice "github.com/fabric8-services/fabric8-auth/authorization/space/service"
teamservice "github.com/fabric8-services/fabric8-auth/authorization/team/service"
tokenservice "github.com/fabric8-services/fabric8-auth/authorization/token/service"
"github.com/fabric8-services/fabric8-auth/configuration"
"github.com/fabric8-services/fabric8-auth/log"
notificationservice "github.com/fabric8-services/fabric8-auth/notification/service"
Expand Down Expand Up @@ -170,18 +171,22 @@ func (f *ServiceFactory) getContext() context.ServiceContext {
return f.contextProducer()
}

func (f *ServiceFactory) OrganizationService() service.OrganizationService {
return organizationservice.NewOrganizationService(f.getContext())
}

func (f *ServiceFactory) InvitationService() service.InvitationService {
return invitationservice.NewInvitationService(f.getContext(), f.config)
}

func (f *ServiceFactory) OrganizationService() service.OrganizationService {
return organizationservice.NewOrganizationService(f.getContext())
}

func (f *ServiceFactory) PermissionService() service.PermissionService {
return permissionservice.NewPermissionService(f.getContext())
}

func (f *ServiceFactory) PrivilegeCacheService() service.PrivilegeCacheService {
return permissionservice.NewPrivilegeCacheService(f.getContext(), f.config)
}

func (f *ServiceFactory) ResourceService() service.ResourceService {
return resourceservice.NewResourceService(f.getContext())
}
Expand All @@ -190,14 +195,18 @@ func (f *ServiceFactory) RoleManagementService() service.RoleManagementService {
return roleservice.NewRoleManagementService(f.getContext())
}

func (f *ServiceFactory) SpaceService() service.SpaceService {
return spaceservice.NewSpaceService(f.getContext())
}

func (f *ServiceFactory) TeamService() service.TeamService {
return teamservice.NewTeamService(f.getContext())
}

func (f *ServiceFactory) TokenService() service.TokenService {
return tokenservice.NewTokenService(f.getContext(), f.config)
}

func (f *ServiceFactory) SpaceService() service.SpaceService {
return spaceservice.NewSpaceService(f.getContext())
}

func (f *ServiceFactory) UserService() service.UserService {
return userservice.NewUserService(f.getContext())
}
Expand Down
17 changes: 14 additions & 3 deletions application/service/services.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/fabric8-services/fabric8-auth/app"
"github.com/fabric8-services/fabric8-auth/authorization"
"github.com/fabric8-services/fabric8-auth/authorization/invitation"
permission "github.com/fabric8-services/fabric8-auth/authorization/permission/repository"
resource "github.com/fabric8-services/fabric8-auth/authorization/resource/repository"
"github.com/fabric8-services/fabric8-auth/authorization/role"
rolerepo "github.com/fabric8-services/fabric8-auth/authorization/role/repository"
Expand Down Expand Up @@ -48,6 +49,10 @@ type PermissionService interface {
RequireScope(ctx context.Context, identityID uuid.UUID, resourceID string, scopeName string) error
}

type PrivilegeCacheService interface {
CachedPrivileges(ctx context.Context, identityID uuid.UUID, resourceID string) (*permission.PrivilegeCache, error)
}

type ResourceService interface {
Delete(ctx context.Context, resourceID string) error
Read(ctx context.Context, resourceID string) (*app.Resource, error)
Expand All @@ -71,6 +76,10 @@ type TeamService interface {
ListTeamsForIdentity(ctx context.Context, identityID uuid.UUID) ([]authorization.IdentityAssociation, error)
}

type TokenService interface {
Audit(ctx context.Context, identity *account.Identity, tokenString string, resourceID string) (*string, error)
}

type SpaceService interface {
CreateSpace(ctx context.Context, spaceCreatorIdentityID uuid.UUID, spaceID string) error
DeleteSpace(ctx context.Context, byIdentityID uuid.UUID, spaceID string) error
Expand All @@ -95,13 +104,15 @@ type WITService interface {
//Services creates instances of service layer objects
type Services interface {
InvitationService() InvitationService
NotificationService() NotificationService
OrganizationService() OrganizationService
ResourceService() ResourceService
PermissionService() PermissionService
PrivilegeCacheService() PrivilegeCacheService
ResourceService() ResourceService
RoleManagementService() RoleManagementService
TeamService() TeamService
SpaceService() SpaceService
TeamService() TeamService
TokenService() TokenService
UserService() UserService
NotificationService() NotificationService
WITService() WITService
}
203 changes: 203 additions & 0 deletions authorization/permission/repository/privilege_cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package repository

import (
"context"
"time"

"fmt"
"github.com/fabric8-services/fabric8-auth/errors"
"github.com/fabric8-services/fabric8-auth/gormsupport"
"github.com/fabric8-services/fabric8-auth/log"

"github.com/goadesign/goa"
"github.com/jinzhu/gorm"
errs "github.com/pkg/errors"
"github.com/satori/go.uuid"
"strings"
)

type PrivilegeCache struct {
gormsupport.Lifecycle

// This is the primary key value
PrivilegeCacheID uuid.UUID `sql:"type:uuid" gorm:"primary_key;column:privilege_cache_id"`

IdentityID uuid.UUID `sql:"type:uuid" gorm:"column:identity_id"`

ResourceID string `sql:"type:string" gorm:"column:resource_id"`

Scopes string

Stale bool `sql:"type:boolean"`

ExpiryTime time.Time
}

// TableName overrides the table name settings in Gorm to force a specific table name
// in the database.
func (m PrivilegeCache) TableName() string {
return "privilege_cache"
}

// Returns the scopes as a string array. If scopes is empty, returns an empty array
func (m PrivilegeCache) ScopesAsArray() []string {
if strings.TrimSpace(m.Scopes) == "" {
return []string{}
} else {
return strings.Split(m.Scopes, ",")
}
}

// GormPrivilegeCacheRepository is the implementation of the storage interface for Resource.
type GormPrivilegeCacheRepository struct {
db *gorm.DB
}

// NewPrivilegeCacheRepository creates a new storage type.
func NewPrivilegeCacheRepository(db *gorm.DB) PrivilegeCacheRepository {
return &GormPrivilegeCacheRepository{db: db}
}

func (m *GormPrivilegeCacheRepository) TableName() string {
return "privilege_cache"
}

// PrivilegeCacheRepository represents the storage interface.
type PrivilegeCacheRepository interface {
CheckExists(ctx context.Context, privilegeCacheID uuid.UUID) (bool, error)
Load(ctx context.Context, privilegeCacheID uuid.UUID) (*PrivilegeCache, error)
Create(ctx context.Context, cache *PrivilegeCache) error
Save(ctx context.Context, cache *PrivilegeCache) error
Delete(ctx context.Context, privilegeCacheID uuid.UUID) error
FindForIdentityResource(ctx context.Context, identityID uuid.UUID, resourceID string) (*PrivilegeCache, error)
}

// CheckExists returns true if the given ID exists otherwise returns an error
func (m *GormPrivilegeCacheRepository) CheckExists(ctx context.Context, privilegeCacheID uuid.UUID) (bool, error) {
defer goa.MeasureSince([]string{"goa", "db", "privilege_cache", "exists"}, time.Now())

var exists bool
query := fmt.Sprintf(`
SELECT EXISTS (
SELECT 1 FROM %[1]s
WHERE
privilege_cache_id=$1
)`, m.TableName())

err := m.db.CommonDB().QueryRow(query, privilegeCacheID).Scan(&exists)
if err == nil && !exists {
return exists, errors.NewNotFoundError(m.TableName(), fmt.Sprintf("%s", privilegeCacheID.String()))
}
if err != nil {
return false, errors.NewInternalError(ctx, errs.Wrapf(err, "unable to verify if %s exists", m.TableName()))
}
return exists, nil
}

// Load returns a single PrivilegeCache as a Database Model
func (m *GormPrivilegeCacheRepository) Load(ctx context.Context, privilegeCacheID uuid.UUID) (*PrivilegeCache, error) {
defer goa.MeasureSince([]string{"goa", "db", "privilege_cache", "load"}, time.Now())

var native PrivilegeCache
err := m.db.Table(m.TableName()).Where("privilege_cache_id = ?", privilegeCacheID).Find(&native).Error
if err == gorm.ErrRecordNotFound {
return nil, errs.WithStack(errors.NewNotFoundError(m.TableName(), fmt.Sprintf("%s", privilegeCacheID.String())))
}

return &native, errs.WithStack(err)
}

// Create creates a new record.
func (m *GormPrivilegeCacheRepository) Create(ctx context.Context, privilegeCache *PrivilegeCache) error {
defer goa.MeasureSince([]string{"goa", "db", "privilege_cache", "create"}, time.Now())

if privilegeCache.PrivilegeCacheID == uuid.Nil {
privilegeCache.PrivilegeCacheID = uuid.NewV4()
}

err := m.db.Create(privilegeCache).Error
if err != nil {
if gormsupport.IsUniqueViolation(err, "privilege_cache_pkey") {
return errors.NewDataConflictError(fmt.Sprintf("privilege cache with ID %s already exists",
privilegeCache.PrivilegeCacheID))
}

log.Error(ctx, map[string]interface{}{
"privilege_cache_id": privilegeCache.PrivilegeCacheID,
"err": err,
}, "unable to create the privilege cache")
return errs.WithStack(err)
}

log.Info(ctx, map[string]interface{}{
"privilege_cache_id": privilegeCache.PrivilegeCacheID,
}, "Privilege cache created!")
return nil
}

// Save modifies a single record.
func (m *GormPrivilegeCacheRepository) Save(ctx context.Context, privilegeCache *PrivilegeCache) error {
defer goa.MeasureSince([]string{"goa", "db", "privilege_cache", "save"}, time.Now())

obj, err := m.Load(ctx, privilegeCache.PrivilegeCacheID)
if err != nil {
log.Error(ctx, map[string]interface{}{
"privilege_cache_id": privilegeCache.PrivilegeCacheID,
"err": err,
}, "unable to update privilege cache")
return errs.WithStack(err)
}

err = m.db.Model(obj).Updates(privilegeCache).Error
if err != nil {
log.Error(ctx, map[string]interface{}{
"privilege_cache_id": privilegeCache.PrivilegeCacheID,
"err": err,
}, "unable to update the privilege cache")

return errs.WithStack(err)
}

log.Debug(ctx, map[string]interface{}{
"privilege_cache_id": privilegeCache.PrivilegeCacheID,
}, "Privilege cache saved!")

return nil
}

// Delete removes a single record.
func (m *GormPrivilegeCacheRepository) Delete(ctx context.Context, privilegeCacheID uuid.UUID) error {
defer goa.MeasureSince([]string{"goa", "db", "privilege_cache", "delete"}, time.Now())

obj := PrivilegeCache{PrivilegeCacheID: privilegeCacheID}
result := m.db.Delete(obj)

if result.Error != nil {
log.Error(ctx, map[string]interface{}{
"privilege_cache_id": privilegeCacheID,
"err": result.Error,
}, "unable to delete the privilege cache")
return errs.WithStack(result.Error)
}
if result.RowsAffected == 0 {
return errors.NewNotFoundError("privilege_cache", fmt.Sprintf("%s", privilegeCacheID.String()))
}

log.Debug(ctx, map[string]interface{}{
"privilege_cache_id": privilegeCacheID,
}, "Privilege cache deleted!")

return nil
}

func (m *GormPrivilegeCacheRepository) FindForIdentityResource(ctx context.Context, identityID uuid.UUID, resourceID string) (*PrivilegeCache, error) {
defer goa.MeasureSince([]string{"goa", "db", "privilege_cache", "FindForIdentityResource"}, time.Now())

var native PrivilegeCache
err := m.db.Table(m.TableName()).Where("identity_id = ? AND resource_id = ?", identityID, resourceID).Find(&native).Error
if err == gorm.ErrRecordNotFound {
return nil, errors.NewNotFoundError(m.TableName(), fmt.Sprintf("identity_id:%s,resource_id:%s", identityID.String(), resourceID))
}

return &native, errs.WithStack(err)
}
Loading

0 comments on commit 62cc9ff

Please sign in to comment.