From 54ad9ad8f52f3626158e42814247085f582b89a7 Mon Sep 17 00:00:00 2001 From: bugoutianzhen123 <2804366305@qq.com> Date: Wed, 10 Sep 2025 12:44:13 +0800 Subject: [PATCH 1/7] feat: add session manager and update README --- README.md | 131 +++++++++++++++++++++++---- session.go | 167 +++++++++++++++++++++++++++++++++++ ucon_enforcer.go | 176 ++++++++++++++----------------------- ucon_enforcer_b_test.go | 14 +++ ucon_enforcer_interface.go | 2 +- ucon_enforcer_test.go | 109 +++++++++++++---------- 6 files changed, 427 insertions(+), 172 deletions(-) create mode 100644 session.go diff --git a/README.md b/README.md index 7951bf9..77280e1 100644 --- a/README.md +++ b/README.md @@ -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() { @@ -38,7 +108,7 @@ func main() { condition := &ucon.Condition{ ID: "location_condition", Name: "location", - Type: "always", + Kind: "always", Expr: "office", } uconE.AddCondition(condition) @@ -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) @@ -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 { + // reused + fmt.Println("session reused 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) } @@ -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 @@ -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. diff --git a/session.go b/session.go new file mode 100644 index 0000000..7402e5e --- /dev/null +++ b/session.go @@ -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 +} diff --git a/ucon_enforcer.go b/ucon_enforcer.go index 527064f..23b6018 100644 --- a/ucon_enforcer.go +++ b/ucon_enforcer.go @@ -28,23 +28,12 @@ import ( // UconEnforcer UCON enforcer that wraps casbin.Enforcer and extends UCON functionality. type UconEnforcer struct { *casbin.Enforcer // Embed casbin.Enforcer for backward compatibility - sessions map[string]Session + sessions *SessionManager conditions map[string]Condition obligations map[string]Obligation monitoringActive map[string]bool // Track which sessions are being monitored - mu sync.RWMutex -} - -type Session struct { - ID string - Subject string - Action string - Object string - Attributes map[string]interface{} - Active bool - StartTime time.Time - EndTime time.Time + mu sync.RWMutex } type Condition struct { @@ -63,101 +52,76 @@ type Obligation struct { // NewUconEnforcer creates a new UCON enforcer. func NewUconEnforcer(e *casbin.Enforcer) IUconEnforcer { + sm := NewSessionManager() + return &UconEnforcer{ Enforcer: e, - sessions: make(map[string]Session), + sessions: sm, conditions: make(map[string]Condition), obligations: make(map[string]Obligation), monitoringActive: make(map[string]bool), + mu: sync.RWMutex{}, } } // EnforceWithSession performs enforcement with session context. -func (u *UconEnforcer) EnforceWithSession(sessionID string) (bool, error) { +func (u *UconEnforcer) EnforceWithSession(sessionID string) (*Session, error) { // Get session information session, err := u.GetSession(sessionID) if err != nil { - return false, err + return nil, err } // Check if session is active - if !session.Active { - return false, errors.New("session is not active") + if !session.IfActive() { + return nil, errors.New("session is not active") } // 1. Evaluate conditions first conditionsOk, err := u.EvaluateConditions(sessionID) if err != nil { - return false, err + return nil, err } if !conditionsOk { - return false, nil + return nil, nil } - // 2. Execute pre-access obligations (访问前义务) + // 2. Execute pre-access obligations err = u.ExecuteObligationsByType(sessionID, "pre") if err != nil { // Pre-access obligations failure should deny access fmt.Printf("Error: Failed to execute pre-access obligations: %v\n", err) - return false, err + return nil, err } // 3. Perform basic Casbin policy enforcement - ok, err := u.Enforce(session.Subject, session.Object, session.Action) + ok, err := u.Enforce(session.GetSubject(), session.GetObject(), session.GetAction()) if err != nil { - return false, err + return nil, err } // 4. Start monitoring if access is granted if ok { // Start monitoring for ongoing obligations _ = u.StartMonitoring(sessionID) + } else { + return nil, nil } - return ok, nil + return session, nil } // CreateSession creates a new session. func (u *UconEnforcer) CreateSession(sub string, act string, obj string, attributes map[string]interface{}) (string, error) { - // Generate session ID - sessionID := fmt.Sprintf("session_%d", time.Now().UnixNano()) - - // Create session - session := Session{ - ID: sessionID, - Subject: sub, - Action: act, - Object: obj, - Active: true, - Attributes: attributes, - StartTime: time.Now(), - } - u.mu.Lock() - defer u.mu.Unlock() - u.sessions[sessionID] = session - return sessionID, nil + return u.sessions.CreateSession(sub, act, obj, attributes) } // GetSession retrieves session information. func (u *UconEnforcer) GetSession(sessionID string) (*Session, error) { - u.mu.RLock() - defer u.mu.RUnlock() - session, exists := u.sessions[sessionID] - if !exists { - return nil, errors.New("session not found") - } - return &session, nil + return u.sessions.GetSessionById(sessionID) } func (u *UconEnforcer) UpdateSessionAttribute(sessionID string, key string, val interface{}) error { - u.mu.Lock() - defer u.mu.Unlock() - session, exists := u.sessions[sessionID] - if !exists { - return errors.New("session not found") - } - session.Attributes[key] = val - u.sessions[sessionID] = session - return nil + return u.sessions.UpdateSessionAttribute(sessionID, key, val) } // RevokeSession revokes a session. @@ -166,15 +130,14 @@ func (u *UconEnforcer) RevokeSession(sessionID string) error { if err != nil { return err } + if session.IfActive() { + return errors.New("session is active, cannot be revoked") + } - if !session.Active { - return errors.New("session has be closed") + if err := u.sessions.DeleteSession(sessionID); err != nil { + return err } - session.Active = false - session.EndTime = time.Now() - u.mu.Lock() - defer u.mu.Unlock() - u.sessions[sessionID] = *session + return nil } @@ -232,7 +195,7 @@ func (u *UconEnforcer) evaluateCondition(condition *Condition, session *Session) } func (u *UconEnforcer) checkLocation(expr string, session *Session) (bool, error) { - location, ok := session.Attributes["location"].(string) + location, ok := session.GetAttribute("location").(string) if !ok { return false, errors.New("location attribute not found or not a string") } @@ -241,7 +204,7 @@ func (u *UconEnforcer) checkLocation(expr string, session *Session) (bool, error } func (u *UconEnforcer) checkVipLevel(expr string, session *Session) (bool, error) { - vipLevel, ok := session.Attributes["vip_level"].(int) + vipLevel, ok := session.GetAttribute("vip_level").(int) if !ok { return false, fmt.Errorf("vip_level attribute not found or not an integer") } @@ -321,51 +284,52 @@ func (u *UconEnforcer) executeUserAuthentication(expr string, session *Session) key := strings.TrimSpace(parts[0]) expectedValue := strings.TrimSpace(parts[1]) - actualValue := session.Attributes[key] + actualValue := session.GetAttribute(key) if actualValue != expectedValue { return fmt.Errorf("user %s authentication failed: %s (expected: %s, actual: %s)", - session.Subject, expr, expectedValue, actualValue) + session.GetSubject(), expr, expectedValue, actualValue) } - fmt.Printf("[AUTH] User %s authentication verification passed: %s\n", session.Subject, expr) + fmt.Printf("[AUTH] User %s authentication verification passed: %s\n", session.GetSubject(), expr) return nil } func (u *UconEnforcer) executeVipValidation(expr string, session *Session) error { - vipLevel := session.Attributes["vip_level"] - vipExpiry := session.Attributes["vip_expiry"] - - if vipLevel == "" { - return fmt.Errorf("user %s is not a VIP user", session.Subject) + vipLevel := session.GetAttribute("vip_level") + vipExpiry := session.GetAttribute("vip_expiry") + if vipLevel == NormalStopReason { + return fmt.Errorf("user %s is not a VIP user", session.GetSubject()) } - if vipExpiry == "expired" { - return fmt.Errorf("user %s VIP membership has expired", session.Subject) + return fmt.Errorf("user %s VIP membership has expired", session.GetSubject()) } - fmt.Printf("[VIP] User %s VIP status is valid (level: %s)\n", session.Subject, vipLevel) + fmt.Printf("[VIP] User %s VIP status is valid (level: %s)\n", session.GetSubject(), vipLevel) return nil } func (u *UconEnforcer) executeAccessLogging(expr string, session *Session) error { - fmt.Printf("[ACCESS LOG] %s: %s -> %s\n", expr, session.Subject, session.Object) + fmt.Printf("[ACCESS LOG] %s: %s -> %s\n", expr, session.GetSubject(), session.GetObject()) return nil } // StartMonitoring starts monitoring a session. func (u *UconEnforcer) StartMonitoring(sessionID string) error { // Check if session exists - _, exists := u.sessions[sessionID] - if !exists { + session, err := u.GetSession(sessionID) + if err != nil { return errors.New("session not found") } + u.mu.Lock() if u.monitoringActive[sessionID] { return nil } - u.monitoringActive[sessionID] = true - go u.monitorSession(sessionID) + u.mu.Unlock() + + go u.monitorSession(session) + fmt.Println("[MONITOR] Monitoring started") return nil } @@ -381,61 +345,53 @@ func (u *UconEnforcer) StopMonitoring(sessionID string) error { fmt.Printf("Warning: Failed to execute post-access obligations during session revocation: %v\n", err) } - if err := u.RevokeSession(sessionID); err != nil { - fmt.Printf("Warning: Failed to revoke session: %v\n", err) - } + _ = session.Stop(NormalStopReason) - fmt.Printf("[MONITOR] Stopped monitoring session %s for %s\n", sessionID, session.Subject) + fmt.Printf("[MONITOR] Stopped monitoring session %s for %s\n", sessionID, session.GetSubject()) return nil } // monitorSession continuously monitors a session. -func (u *UconEnforcer) monitorSession(sessionID string) { +func (u *UconEnforcer) monitorSession(session *Session) { ticker := time.NewTicker(200 * time.Millisecond) defer ticker.Stop() for range ticker.C { // Check if monitoring is still active - isActive := u.monitoringActive[sessionID] + isActive := u.monitoringActive[session.GetId()] if !isActive { return } - session, err := u.GetSession(sessionID) - if err != nil { - fmt.Printf("[MONITOR] Error getting session %s: %v\n", sessionID, err) - u.monitoringActive[sessionID] = false - return - } - if !session.Active { - u.monitoringActive[sessionID] = false + if !session.IfActive() { + u.mu.Lock() + u.monitoringActive[session.GetId()] = false + u.mu.Unlock() return } // Check conditions during ongoing access - conditionsOk, err := u.EvaluateConditions(sessionID) + conditionsOk, err := u.EvaluateConditions(session.GetId()) if err != nil { - fmt.Printf("[MONITOR] Error evaluating conditions for session %s: %v\n", sessionID, err) - continue + reason := fmt.Sprintf("Error evaluating conditions for session %s: %v\n", session.GetId(), err) + _ = session.Stop(reason) + return } if !conditionsOk { - fmt.Printf("[MONITOR] Conditions no longer met for session %s, revoking...\n", sessionID) - revokeErr := u.RevokeSession(sessionID) - if revokeErr != nil { - fmt.Printf("[MONITOR] Error revoking session %s: %v\n", sessionID, revokeErr) - } - u.monitoringActive[sessionID] = false + reason := fmt.Sprintf("Conditions no longer met for session %s, revoking...\n", session.GetId()) + _ = session.Stop(reason) return } // Execute ongoing obligations during continuous authorization - err = u.ExecuteObligationsByType(sessionID, "ongoing") + err = u.ExecuteObligationsByType(session.GetId(), "ongoing") if err != nil { - fmt.Printf("[MONITOR] Warning: Failed to execute ongoing obligations for session %s: %v\n", sessionID, err) - // Continue monitoring even if ongoing obligations fail + reason := fmt.Sprintf("Failed to execute ongoing obligations for session %s: %v\n", session.GetId(), err) + _ = session.Stop(reason) + return } - fmt.Printf("[MONITOR] Session %s is still valid\n", sessionID) + fmt.Printf("[MONITOR] Session %s is still valid\n", session.GetId()) } } diff --git a/ucon_enforcer_b_test.go b/ucon_enforcer_b_test.go index a96217b..27eb775 100644 --- a/ucon_enforcer_b_test.go +++ b/ucon_enforcer_b_test.go @@ -1,3 +1,17 @@ +// 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 "testing" diff --git a/ucon_enforcer_interface.go b/ucon_enforcer_interface.go index 6aedea9..a1dce6a 100644 --- a/ucon_enforcer_interface.go +++ b/ucon_enforcer_interface.go @@ -24,7 +24,7 @@ type IUconEnforcer interface { casbin.IEnforcer // Enhanced enforcement with session context - EnforceWithSession(sessionID string) (bool, error) + EnforceWithSession(sessionID string) (*Session, error) // Session management CreateSession(sub string, act string, obj string, attributes map[string]interface{}) (string, error) diff --git a/ucon_enforcer_test.go b/ucon_enforcer_test.go index c1e00a4..5689e9e 100644 --- a/ucon_enforcer_test.go +++ b/ucon_enforcer_test.go @@ -15,6 +15,7 @@ package ucon import ( + "fmt" "testing" "time" @@ -69,20 +70,28 @@ func TestSession(t *testing.T) { if err != nil { t.Fatalf("Failed to get session: %v", err) } - if session.Subject != "alice" { - t.Errorf("Expected subject 'alice', got '%s'", session.Subject) + if session.GetSubject() != "alice" { + t.Errorf("Expected subject 'alice', got '%s'", session.GetSubject()) } - if session.Action != "read" { - t.Errorf("Expected action 'read', got '%s'", session.Action) + if session.GetAction() != "read" { + t.Errorf("Expected action 'read', got '%s'", session.GetAction()) } - if session.Object != "document1" { - t.Errorf("Expected object 'document1', got '%s'", session.Object) + if session.GetObject() != "document1" { + t.Errorf("Expected object 'document1', got '%s'", session.GetObject()) } - if !session.Active { + if !session.IfActive() { t.Error("Session should be active") } // Test RevokeSession + err = uconE.RevokeSession(sessionID) + if err == nil { + t.Fatalf("session shuldn't be closed: %v", err) + } + + _ = session.Stop(NormalStopReason) + time.Sleep(500 * time.Millisecond) + err = uconE.RevokeSession(sessionID) if err != nil { t.Fatalf("Failed to revoke session: %v", err) @@ -90,11 +99,8 @@ func TestSession(t *testing.T) { // Verify session is revoked session, err = uconE.GetSession(sessionID) - if err != nil { - t.Fatalf("Failed to get session after revocation: %v", err) - } - if session.Active { - t.Error("Session should be inactive after revocation") + if session != nil { + t.Fatalf("session revoke failed: %v", err) } } @@ -226,40 +232,35 @@ func TestEnforceWithSession(t *testing.T) { t.Fatalf("Failed to create session: %v", err) } - result, err := uconE.EnforceWithSession(sessionID) - if !result { + session, err := uconE.EnforceWithSession(sessionID) + if session == nil { + // refused t.Fatalf("Failed to enforce: %v", err) } if err != nil { t.Fatalf("Failed to enforce with session: %v", err) } - // Simulate ongoing access for a short period - time.Sleep(500 * time.Millisecond) - - session, err := uconE.GetSession(sessionID) - if err != nil { - t.Fatalf("Failed to get session: %v", err) - } - if !session.Active { - t.Error("Session should be active during access") - } - - err = uconE.StopMonitoring(sessionID) - if err != nil { - t.Fatalf("Failed to stop monitoring: %v", err) - } - - session, err = uconE.GetSession(sessionID) - if err != nil { - t.Fatalf("Failed to get session after revocation: %v", err) - } - if session.Active { - t.Error("Session should not be active after revocation") - } + go func() { + for { + if !session.IfActive() { + if session.GetStopReason() == NormalStopReason { + break + } + //the session is stoped + uconE.RevokeSession(sessionID) + break + } + time.Sleep(200 * time.Millisecond) + } + }() + + fmt.Printf("%s %s %s is enforced\n", session.GetSubject(), session.GetAction(), session.GetObject()) + time.Sleep(2 * time.Second) + _ = uconE.StopMonitoring(sessionID) } -func TestSessionRevokedDuringAccess(t *testing.T) { +func TestSessionRefusedDuringAccess(t *testing.T) { uconE := GetUconEnforcer() condition := &Condition{ @@ -276,17 +277,33 @@ func TestSessionRevokedDuringAccess(t *testing.T) { "log_level": "detailed", }) - if err := uconE.StartMonitoring(sessionID); err != nil { - t.Fatalf("Failed to start monitoring: %v", err) + session, err := uconE.EnforceWithSession(sessionID) + if session == nil { + // refused + t.Fatalf("Failed to enforce: %v", err) + } + if err != nil { + t.Fatalf("Failed to enforce with session: %v", err) } - time.Sleep(300 * time.Millisecond) - uconE.UpdateSessionAttribute(sessionID, "location", "home") + go func() { + for { + if !session.IfActive() { + //user can chose how to stop the session + uconE.RevokeSession(sessionID) + fmt.Printf("%s %s %s is stopped\n", session.GetSubject(), session.GetAction(), session.GetObject()) + break + } + time.Sleep(200 * time.Millisecond) + } + }() - time.Sleep(300 * time.Millisecond) + time.Sleep(500 * time.Millisecond) + session.UpdateAttribute("location", "home") + time.Sleep(1 * time.Second) - session, err := uconE.GetSession(sessionID) - if err == nil && session.Active { - t.Error("Expected session to be revoked due to condition change") + _, err = uconE.GetSession(sessionID) + if err == nil { + t.Error("Expected session to be deleted after revocation") } } From 479467ecd31d68146a0b0c405cee4d45e3dfadd1 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Sun, 14 Sep 2025 15:25:32 +0800 Subject: [PATCH 2/7] Update ucon_enforcer_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ucon_enforcer_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ucon_enforcer_test.go b/ucon_enforcer_test.go index 5689e9e..390486c 100644 --- a/ucon_enforcer_test.go +++ b/ucon_enforcer_test.go @@ -86,7 +86,7 @@ func TestSession(t *testing.T) { // Test RevokeSession err = uconE.RevokeSession(sessionID) if err == nil { - t.Fatalf("session shuldn't be closed: %v", err) + t.Fatalf("session shouldn't be closed: %v", err) } _ = session.Stop(NormalStopReason) From 418621628a570e08e52172fcccb23f2384ab22dc Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Sun, 14 Sep 2025 15:25:38 +0800 Subject: [PATCH 3/7] Update ucon_enforcer_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ucon_enforcer_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ucon_enforcer_test.go b/ucon_enforcer_test.go index 390486c..24c9a20 100644 --- a/ucon_enforcer_test.go +++ b/ucon_enforcer_test.go @@ -100,7 +100,7 @@ func TestSession(t *testing.T) { // Verify session is revoked session, err = uconE.GetSession(sessionID) if session != nil { - t.Fatalf("session revoke failed: %v", err) + t.Fatalf("session revocation failed: %v", err) } } From 9d8a1a2f65233f713c78d33a1580b2b730407c6c Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Sun, 14 Sep 2025 15:25:43 +0800 Subject: [PATCH 4/7] Update ucon_enforcer_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ucon_enforcer_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ucon_enforcer_test.go b/ucon_enforcer_test.go index 24c9a20..5bd076a 100644 --- a/ucon_enforcer_test.go +++ b/ucon_enforcer_test.go @@ -247,7 +247,7 @@ func TestEnforceWithSession(t *testing.T) { if session.GetStopReason() == NormalStopReason { break } - //the session is stoped + //the session is stopped uconE.RevokeSession(sessionID) break } From 819d5fec6b25a90eda88e629392517b6b06df238 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Sun, 14 Sep 2025 15:25:49 +0800 Subject: [PATCH 5/7] Update ucon_enforcer_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ucon_enforcer_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ucon_enforcer_test.go b/ucon_enforcer_test.go index 5bd076a..8775045 100644 --- a/ucon_enforcer_test.go +++ b/ucon_enforcer_test.go @@ -289,7 +289,7 @@ func TestSessionRefusedDuringAccess(t *testing.T) { go func() { for { if !session.IfActive() { - //user can chose how to stop the session + //user can choose how to stop the session uconE.RevokeSession(sessionID) fmt.Printf("%s %s %s is stopped\n", session.GetSubject(), session.GetAction(), session.GetObject()) break From abc8325d00054ba920fac3debb745f71a432ff27 Mon Sep 17 00:00:00 2001 From: Zhendong Liu <148350506+bugoutianzhen123@users.noreply.github.com> Date: Mon, 15 Sep 2025 10:22:43 +0800 Subject: [PATCH 6/7] Update ucon_enforcer.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- ucon_enforcer.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ucon_enforcer.go b/ucon_enforcer.go index 23b6018..0437229 100644 --- a/ucon_enforcer.go +++ b/ucon_enforcer.go @@ -297,7 +297,7 @@ func (u *UconEnforcer) executeUserAuthentication(expr string, session *Session) func (u *UconEnforcer) executeVipValidation(expr string, session *Session) error { vipLevel := session.GetAttribute("vip_level") vipExpiry := session.GetAttribute("vip_expiry") - if vipLevel == NormalStopReason { + if vipLevel == "" { return fmt.Errorf("user %s is not a VIP user", session.GetSubject()) } if vipExpiry == "expired" { From b5acea8b959109d1b3a9667e7c43bc058ae4eabe Mon Sep 17 00:00:00 2001 From: Zhendong Liu <148350506+bugoutianzhen123@users.noreply.github.com> Date: Mon, 15 Sep 2025 10:22:57 +0800 Subject: [PATCH 7/7] Update README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 77280e1..c68edd1 100644 --- a/README.md +++ b/README.md @@ -131,8 +131,8 @@ func main() { // UCON session-based enforcement session, err := uconE.EnforceWithSession(sessionID) if session == nil { - // reused - fmt.Println("session reused because: ",err ) + // refused + fmt.Println("session refused because: ",err ) } go func() {