Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
131 changes: 116 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,90 @@ Casbin-UCON extends Casbin with UCON (Usage Control) capabilities, enabling:
- **Obligation execution** for required actions
- **Continuous monitoring** for ongoing authorization

## Prerequisites

- Basic knowledge of [Casbin](https://github.com/casbin/casbin) is required,
since Casbin-UCON extends Casbin with session-based usage control.

## Installation

```bash
go get github.com/casbin/casbin-ucon
```

## Continuous Authorization Behavior

It's important to understand how continuous authorization works in Casbin-UCON:

1. EnforceWithSession(sessionID) performs pre-checks (pre-conditions and pre-obligations) and automatically starts monitoring for ongoing conditions and obligations.

2. StartMonitoring(sessionID) only starts monitoring without pre-checks.

3. If a session no longer satisfies the conditions, session.IfActive() will return false, and you can use session.GetStopReason() to determine why the session stopped.

4. Your application is responsible for handling these notifications and deciding how to terminate the session.

Always call StopMonitoring() to clean up resources when done.
Example:

```go
go func() {
for {
if !session.IfActive() {
if session.GetStopReason() == ucon.NormalStopReason {
// NormalStopReason means the session was stopped by user code calling StopMonitoring().
break
}
//TODO
//decide how to handle session termination yourself
// For example, clean up resources, close connections, write logs, notify the frontend, etc.
fmt.Printf("%s %s %s is stopped because: %s\n", session.GetSubject(), session.GetAction(), session.GetObject(),session.GetStopReason())
break
}
time.Sleep(200 * time.Millisecond)
}
}()
```

## Quick Start

Casbin-UCON requires standard Casbin configuration files:

- **model.conf**: defines the access control model (RBAC, ABAC, etc.)
- **policy.csv**: defines the access policies

For example:

**model.conf**

```conf
[request_definition]
r = sub, obj, act

[policy_definition]
p = sub, obj, act

[policy_effect]
e = some(where (p.eft == allow))

[matchers]
m = r.sub == p.sub && r.obj == p.obj && r.act == p.act
```

**policy.csv**

```csv
p, alice, document1, read
```

```go
package main

import (
"github.com/casbin/casbin/v2"
"github.com/casbin/casbin-ucon"
"fmt"
"time"
)

func main() {
Expand All @@ -38,7 +108,7 @@ func main() {
condition := &ucon.Condition{
ID: "location_condition",
Name: "location",
Type: "always",
Kind: "always",
Expr: "office",
}
uconE.AddCondition(condition)
Expand All @@ -47,7 +117,7 @@ func main() {
obligation := &ucon.Obligation{
ID: "post_log",
Name: "access_logging",
Type: "post",
Kind: "post",
Expr: "log_level:detailed",
}
uconE.AddObligation(obligation)
Expand All @@ -59,16 +129,37 @@ func main() {
})

// UCON session-based enforcement
if res, err := uconE.EnforceWithSession(sessionID); res {
// the session has started
}else{
// deny the request, show an error
session, err := uconE.EnforceWithSession(sessionID)
if session == nil {
// refused
fmt.Println("session refused because: ",err )
}

go func() {
for {
if !session.IfActive() {
if session.GetStopReason() == ucon.NormalStopReason {
break
}
//TODO
//decide how to handle session termination yourself
// For example, clean up resources, close connections, write logs, notify the frontend, etc.
fmt.Printf("%s %s %s is stopped because: %s\n", session.GetSubject(), session.GetAction(), session.GetObject(),session.GetStopReason())
break
}
time.Sleep(200 * time.Millisecond)
}
/*
ongoing access
*/

// Stop the seesion
}()

/*
alice read document1

//you could change the attribute by:
session.UpdateAttribute("location", "home")
*/


// Stop the session
_ = uconE.StopMonitoring(sessionID)

}
Expand All @@ -78,19 +169,19 @@ func main() {

```go
// Enhanced enforcement
EnforceWithSession(sessionID string) (bool, error)
EnforceWithSession(sessionID string) (*Session, error)

// Session management
CreateSession(subject, action, object string, attributes map[string]interface{}) (string, error)
GetSession(sessionID string) (*SessionImpl, error)
GetSession(sessionID string) (*Session, error)
UpdateSessionAttribute(sessionID string, key string, val interface{}) error
RevokeSession(sessionID string) error

// Condition management
AddCondition(condition *ConditionImpl) error
AddCondition(condition *Condition) error
EvaluateConditions(sessionID string) (bool, error)
// Obligation management
AddObligation(obligation *ObligationImpl) error
AddObligation(obligation *Obligation) error
ExecuteObligations(sessionID string) error
ExecuteObligationsByType(sessionID string, phase string) error

Expand All @@ -110,6 +201,16 @@ StopMonitoring(sessionID string) error
- Foundation for conditions, obligations, and monitoring
- Full Casbin compatibility

## Future Plans

- Enhanced Condition & Obligation Management – Allow more flexible and customizable conditions and obligations.

- Improved Session Management – Additional features for session lifecycle and attribute handling.

- Advanced Monitoring – Configurable monitoring options for ongoing authorization and obligations.

- Comprehensive Documentation & Examples – Expanded guides, usage examples, and best practices.

## License

Apache 2.0 License - see [LICENSE](LICENSE) for details.
167 changes: 167 additions & 0 deletions session.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
// Copyright 2025 The casbin Authors. All Rights Reserved.
//
// 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.

package ucon

import (
"fmt"
"sync"
"time"
)

type Session struct {
id string
subject string
action string
object string

attributes map[string]interface{}
active bool
startTime time.Time
endTime time.Time
stopReason string

mutex sync.RWMutex
}

const (
NormalStopReason = ""
)

func (s *Session) GetId() string {
return s.id
}

func (s *Session) GetSubject() string {
return s.subject
}

func (s *Session) GetAction() string {
return s.action
}

func (s *Session) GetObject() string {
return s.object
}

func (s *Session) GetAttribute(key string) interface{} {
s.mutex.RLock()
defer s.mutex.RUnlock()
return s.attributes[key]
}

func (s *Session) UpdateAttribute(key string, val interface{}) error {
s.mutex.Lock()
defer s.mutex.Unlock()
s.attributes[key] = val
return nil
}

func (s *Session) Stop(reason string) error {
s.mutex.Lock()
if !s.active {
s.mutex.Unlock()
return fmt.Errorf("session already stopped")
}

s.active = false
s.endTime = time.Now()
s.stopReason = reason
s.mutex.Unlock()
return nil
}

func (s *Session) IfActive() bool {
s.mutex.RLock()
defer s.mutex.RUnlock()
return s.active
}

func (s *Session) GetStopReason() string {
return s.stopReason
}

func (s *Session) GetStartTime() time.Time {
return s.startTime
}

func (s *Session) GetEndTime() time.Time {
return s.endTime
}

func (s *Session) GetDuration() time.Duration {
if s.active {
return time.Since(s.startTime)
}
return s.endTime.Sub(s.startTime)
}

type SessionManager struct {
sessions map[string]*Session
mutex sync.RWMutex
}

func NewSessionManager() *SessionManager {
return &SessionManager{
sessions: make(map[string]*Session),
mutex: sync.RWMutex{},
}
}

func (sm *SessionManager) GetSessionById(id string) (*Session, error) {
sm.mutex.RLock()
defer sm.mutex.RUnlock()
s, exists := sm.sessions[id]
if !exists {
return nil, fmt.Errorf("cannot find session with id %s", id)
}
return s, nil
}

func (sm *SessionManager) CreateSession(sub string, act string, obj string, attributes map[string]interface{}) (string, error) {
sessionID := fmt.Sprintf("session_%d", time.Now().UnixNano())
session := &Session{
id: sessionID,
subject: sub,
action: act,
object: obj,
active: true,
attributes: attributes,
startTime: time.Now(),
mutex: sync.RWMutex{},
}

sm.mutex.Lock()
sm.sessions[sessionID] = session
sm.mutex.Unlock()
return sessionID, nil
}

func (sm *SessionManager) UpdateSessionAttribute(sessionID string, key string, val interface{}) error {
session, err := sm.GetSessionById(sessionID)
if err != nil {
return err
}
if err := session.UpdateAttribute(key, val); err != nil {
return err
}
return nil
}

func (sm *SessionManager) DeleteSession(sessionID string) error {
sm.mutex.Lock()
defer sm.mutex.Unlock()
delete(sm.sessions, sessionID)
return nil
}
Loading
Loading