Skip to content

Conversation

@jrschumacher
Copy link
Member

  • support multi group claims
  • refactor for efficiency
  • refactor for future casbin adapter support

Checklist

  • I have added or updated unit tests
  • I have added or updated integration tests (if appropriate)
  • I have added or updated documentation

Testing Instructions

- support multi group claims
- refactor for efficiency
- refactor for future casbin adapter support
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @jrschumacher, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly upgrades the authorization capabilities by introducing robust support for multiple group claims. This allows for more granular and flexible role-based access control, drawing user roles from various locations within authentication tokens or user profiles. The underlying Casbin integration has been optimized and modularized, paving the way for future extensibility with different policy storage mechanisms.

Highlights

  • Multi-Group Claims Support: The authorization system now supports extracting roles from multiple group claims within a JWT token or user information, enhancing flexibility in defining user permissions.
  • Refactored Casbin Enforcer: The Casbin enforcer has been refactored for improved efficiency and clarity, including precomputing group claim selectors and streamlining the policy enforcement logic.
  • Future Casbin Adapter Support: Changes have been made to prepare the authorization module for easier integration with various Casbin adapters (e.g., SQL), separating CSV policy handling into a dedicated file.
  • Unified Dot Notation Utility: A common Dotnotation utility function has been introduced in the pkg/util package, centralizing logic for safely accessing nested map values using dot-separated keys.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.


A token arrives, claims nested deep, Roles for the user, secrets to keep. Casbin now scans, with logic so keen, Granting access, a well-oiled machine.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for multiple group claims in the authorization logic, which is a great enhancement. The refactoring efforts are commendable, especially the separation of CSV policy building into its own file (casbin_csv.go) and moving the dotNotation utility to a shared package. This improves modularity and prepares the codebase for future extensions, like supporting different Casbin adapters. The test suite has also been significantly improved with more comprehensive and table-driven tests, increasing confidence in the changes. My only suggestion is to address a piece of duplicated code in casbin.go to further improve maintainability.

Comment on lines +184 to 254
// extractRolesFromToken extracts roles from a jwt.Token based on the configured claim path
func (e *Enforcer) extractRolesFromToken(token jwt.Token) []string {
roles := make([]string, 0, defaultRolesCapacity) // preallocate for common case
for _, selectors := range e.groupClaimSelectors {
if len(selectors) == 0 {
continue
}
claim = dotNotation(claimMap, strings.Join(selectors[1:], "."))
if claim == nil {
e.logger.Warn("claim not found",
slog.String("claim", roleClaim),
slog.Any("claims", claim),
)
return nil
claim, exists := token.Get(selectors[0])
if !exists {
continue // skip missing claim, don't log on hot path
}
if len(selectors) > 1 {
claimMap, ok := claim.(map[string]interface{})
if !ok {
continue // skip invalid type
}
claim = util.Dotnotation(claimMap, strings.Join(selectors[1:], "."))
if claim == nil {
continue
}
}
// Inline extractRolesFromClaim for efficiency
switch v := claim.(type) {
case string:
roles = append(roles, v)
case []interface{}:
for _, rr := range v {
if r, ok := rr.(string); ok {
roles = append(roles, r)
}
}
case []string:
roles = append(roles, v...)
}
}
return roles
}

// check the type of the role claim
switch v := claim.(type) {
case string:
roles = append(roles, v)
case []interface{}:
for _, rr := range v {
if r, ok := rr.(string); ok {
roles = append(roles, r)
// extractRolesFromUserInfo extracts roles from a userInfo JSON ([]byte) based on the configured claim path
func (e *Enforcer) extractRolesFromUserInfo(userInfo []byte) []string {
roles := make([]string, 0, defaultRolesCapacity)
if userInfo == nil || len(userInfo) == 0 {
return roles
}
var userInfoMap map[string]interface{}
if err := json.Unmarshal(userInfo, &userInfoMap); err != nil {
return roles // skip logging on hot path
}
for _, selectors := range e.groupClaimSelectors {
if len(selectors) == 0 {
continue
}
claim := util.Dotnotation(userInfoMap, strings.Join(selectors, "."))
if claim == nil {
continue
}
switch v := claim.(type) {
case string:
roles = append(roles, v)
case []interface{}:
for _, rr := range v {
if r, ok := rr.(string); ok {
roles = append(roles, r)
}
}
case []string:
roles = append(roles, v...)
}
default:
e.logger.Warn("could not get claim type",
slog.String("selector", roleClaim),
slog.Any("claims", claim),
)
return nil
}

return roles
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The functions extractRolesFromToken and extractRolesFromUserInfo contain nearly identical logic for extracting role strings from a claim value. This duplication can be reduced by extracting the common logic into a helper function. While the comment on line 205 suggests inlining was done for efficiency, the performance gain is likely negligible for this small function, and extracting it would improve maintainability.

A potential helper function could look like this:

// extractRolesFromClaim is a helper to extract role strings from a claim value.
func (e *Enforcer) extractRolesFromClaim(claim interface{}) []string {
	var roles []string
	switch v := claim.(type) {
	case string:
		if v != "" {
			roles = append(roles, v)
		}
	case []interface{}:
		for _, rr := range v {
			if r, ok := rr.(string); ok && r != "" {
				roles = append(roles, r)
			}
		}
	case []string:
		for _, r := range v {
			if r != "" {
				roles = append(roles, r)
			}
		}
	}
	return roles
}

This helper could then be called from both extractRolesFromToken and extractRolesFromUserInfo to handle the claim value, thus removing the duplicated code.

@github-actions
Copy link
Contributor

Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 153.729841ms

Benchmark authorization.v2.GetMultiResourceDecision Results:

Metric Value
Approved Decision Requests 1000
Denied Decision Requests 0
Total Time 90.796692ms

Benchmark Statistics

Name № Requests Avg Duration Min Duration Max Duration

Bulk Benchmark Results

Metric Value
Total Decrypts 100
Successful Decrypts 100
Failed Decrypts 0
Total Time 371.940861ms
Throughput 268.86 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 38.008903767s
Average Latency 378.979828ms
Throughput 131.55 requests/second

NANOTDF Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 26.740140216s
Average Latency 266.366014ms
Throughput 186.98 requests/second

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants