Skip to content

feat(otdfctl): Auth provider and profile extensions#3736

Draft
elizabethhealy wants to merge 1 commit into
mainfrom
add-metadata-to-otdfctl-profiles
Draft

feat(otdfctl): Auth provider and profile extensions#3736
elizabethhealy wants to merge 1 commit into
mainfrom
add-metadata-to-otdfctl-profiles

Conversation

@elizabethhealy

Copy link
Copy Markdown
Member

Proposed Changes

  • Ability to add a new auth process downstream
  • Ability to store extra data in the profile downstream

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

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 22eda6f1-318b-4940-adda-d96000983c78

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch add-metadata-to-otdfctl-profiles

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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 enhances the extensibility of the otdfctl CLI by decoupling authentication logic and providing a flexible storage mechanism for custom profile data. These changes allow downstream projects to integrate their own authentication providers and store project-specific configuration directly within the existing profile structure, improving modularity and maintainability.

Highlights

  • Pluggable Authentication: Introduced an auth.Provider interface and registration mechanism, allowing downstream projects to implement and register custom authentication flows without modifying the core codebase.
  • Profile Extension Store: Added a namespaced extension store to OtdfctlProfileStore, enabling developers to persist and retrieve arbitrary typed data within profiles using generic helpers.
New Features

🧠 You can now enable Memory (public preview) 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 the 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 counterproductive. 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.


A CLI tool that's quite grand, With auth that you can command. Extensions now stored, With types unexplored, Extending the reach of the land.

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.

@github-actions github-actions Bot added the size/m label Jul 9, 2026

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

Copy link
Copy Markdown
Contributor

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 a pluggable authentication mechanism and custom profile extensions to otdfctl, allowing external projects to register custom auth providers and store arbitrary JSON data in profiles. The review feedback focuses on robustness and thread safety, suggesting several nil checks for the profile store and its configuration to prevent nil pointer dereferences, as well as utilizing a sync.RWMutex to protect the global authProviders map from concurrent access issues.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread otdfctl/pkg/auth/auth.go
Comment on lines 167 to 173
func GetSDKAuthOptionFromProfile(profile *profiles.OtdfctlProfileStore) (sdk.Option, error) {
c := profile.GetAuthCredentials()

switch c.AuthType {
case profiles.AuthTypeClientCredentials:
return sdk.WithClientCredentials(c.ClientID, c.ClientSecret, NormalizeScopes(c.Scopes)), nil
case profiles.AuthTypeAccessToken:
tokenSource := oauth2.StaticTokenSource(buildToken(&c))
return sdk.WithOAuthAccessTokenSource(tokenSource), nil
default:
return nil, ErrInvalidAuthType
provider, err := lookupProvider(profile.GetAuthCredentials().AuthType)
if err != nil {
return nil, err
}
return provider.SDKAuthOption(profile)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Add a nil check for profile to prevent potential nil pointer dereference panics if the function is called with a nil profile store.

Suggested change
func GetSDKAuthOptionFromProfile(profile *profiles.OtdfctlProfileStore) (sdk.Option, error) {
c := profile.GetAuthCredentials()
switch c.AuthType {
case profiles.AuthTypeClientCredentials:
return sdk.WithClientCredentials(c.ClientID, c.ClientSecret, NormalizeScopes(c.Scopes)), nil
case profiles.AuthTypeAccessToken:
tokenSource := oauth2.StaticTokenSource(buildToken(&c))
return sdk.WithOAuthAccessTokenSource(tokenSource), nil
default:
return nil, ErrInvalidAuthType
provider, err := lookupProvider(profile.GetAuthCredentials().AuthType)
if err != nil {
return nil, err
}
return provider.SDKAuthOption(profile)
}
func GetSDKAuthOptionFromProfile(profile *profiles.OtdfctlProfileStore) (sdk.Option, error) {
if profile == nil {
return nil, ErrProfileCredentialsNotFound
}
provider, err := lookupProvider(profile.GetAuthCredentials().AuthType)
if err != nil {
return nil, err
}
return provider.SDKAuthOption(profile)
}

Comment thread otdfctl/pkg/auth/auth.go
Comment on lines 177 to 187
func ValidateProfileAuthCredentials(ctx context.Context, profile *profiles.OtdfctlProfileStore) error {
c := profile.GetAuthCredentials()

switch c.AuthType {
case "":
authType := profile.GetAuthCredentials().AuthType
if authType == "" {
return ErrProfileCredentialsNotFound
case profiles.AuthTypeClientCredentials:
_, err := GetTokenWithClientCreds(ctx, profile.GetEndpoint(), c.ClientID, c.ClientSecret, profile.GetTLSNoVerify(), c.Scopes)
if err != nil {
return err
}
return nil
case profiles.AuthTypeAccessToken:
if !buildToken(&c).Valid() {
return ErrAccessTokenExpired
}
default:
return ErrInvalidAuthType
}
return nil
provider, err := lookupProvider(authType)
if err != nil {
return err
}
return provider.Validate(ctx, profile)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Add a nil check for profile to prevent potential nil pointer dereference panics if the function is called with a nil profile store.

func ValidateProfileAuthCredentials(ctx context.Context, profile *profiles.OtdfctlProfileStore) error {
	if profile == nil {
		return ErrProfileCredentialsNotFound
	}
	authType := profile.GetAuthCredentials().AuthType
	if authType == "" {
		return ErrProfileCredentialsNotFound
	}
	provider, err := lookupProvider(authType)
	if err != nil {
		return err
	}
	return provider.Validate(ctx, profile)
}

Comment thread otdfctl/pkg/auth/auth.go
Comment on lines 190 to 196
func GetTokenWithProfile(ctx context.Context, profile *profiles.OtdfctlProfileStore) (*oauth2.Token, error) {
c := profile.GetAuthCredentials()

switch c.AuthType {
case profiles.AuthTypeClientCredentials:
return GetTokenWithClientCreds(ctx, profile.GetEndpoint(), c.ClientID, c.ClientSecret, profile.GetTLSNoVerify(), c.Scopes)
case profiles.AuthTypeAccessToken:
return buildToken(&c), nil
default:
return nil, ErrInvalidAuthType
provider, err := lookupProvider(profile.GetAuthCredentials().AuthType)
if err != nil {
return nil, err
}
return provider.GetToken(ctx, profile)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Add a nil check for profile to prevent potential nil pointer dereference panics if the function is called with a nil profile store.

func GetTokenWithProfile(ctx context.Context, profile *profiles.OtdfctlProfileStore) (*oauth2.Token, error) {
	if profile == nil {
		return nil, ErrProfileCredentialsNotFound
	}
	provider, err := lookupProvider(profile.GetAuthCredentials().AuthType)
	if err != nil {
		return nil, err
	}
	return provider.GetToken(ctx, profile)
}

Comment on lines +10 to +20
func GetExtension[T any](p *OtdfctlProfileStore, name string) (T, bool, error) {
var v T
raw, ok := p.getRawExtension(name)
if !ok {
return v, false, nil
}
if err := json.Unmarshal(raw, &v); err != nil {
return v, true, err
}
return v, true, nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Add a nil check for p to prevent potential nil pointer dereference panics if the function is called with a nil profile store.

func GetExtension[T any](p *OtdfctlProfileStore, name string) (T, bool, error) {
	var v T
	if p == nil {
		return v, false, nil
	}
	raw, ok := p.getRawExtension(name)
	if !ok {
		return v, false, nil
	}
	if err := json.Unmarshal(raw, &v); err != nil {
		return v, true, err
	}
	return v, true, nil
}

Comment on lines +23 to +29
func SetExtension[T any](p *OtdfctlProfileStore, name string, v T) error {
raw, err := json.Marshal(v)
if err != nil {
return err
}
return p.setRawExtension(name, raw)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Add a nil check for p to prevent potential nil pointer dereference panics if the function is called with a nil profile store.

func SetExtension[T any](p *OtdfctlProfileStore, name string, v T) error {
	if p == nil {
		return ErrProfileConfigEmpty
	}
	raw, err := json.Marshal(v)
	if err != nil {
		return err
	}
	return p.setRawExtension(name, raw)
}

Comment on lines +51 to +61
func (p *OtdfctlProfileStore) ExtensionNames() []string {
if len(p.config.Extensions) == 0 {
return nil
}
names := make([]string, 0, len(p.config.Extensions))
for name := range p.config.Extensions {
names = append(names, name)
}
sort.Strings(names)
return names
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Add a nil check for p and p.config to prevent potential nil pointer dereference panics if the method is called on a nil profile store receiver or if the config is not initialized.

Suggested change
func (p *OtdfctlProfileStore) ExtensionNames() []string {
if len(p.config.Extensions) == 0 {
return nil
}
names := make([]string, 0, len(p.config.Extensions))
for name := range p.config.Extensions {
names = append(names, name)
}
sort.Strings(names)
return names
}
func (p *OtdfctlProfileStore) ExtensionNames() []string {
if p == nil || p.config == nil || len(p.config.Extensions) == 0 {
return nil
}
names := make([]string, 0, len(p.config.Extensions))
for name := range p.config.Extensions {
names = append(names, name)
}
sort.Strings(names)
return names
}

Comment on lines +64 to +70
func (p *OtdfctlProfileStore) getRawExtension(name string) (json.RawMessage, bool) {
if p.config.Extensions == nil {
return nil, false
}
raw, ok := p.config.Extensions[name]
return raw, ok
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Add a nil check for p and p.config to prevent potential nil pointer dereference panics if the method is called on a nil profile store receiver or if the config is not initialized.

func (p *OtdfctlProfileStore) getRawExtension(name string) (json.RawMessage, bool) {
	if p == nil || p.config == nil {
		return nil, false
	}
	if p.config.Extensions == nil {
		return nil, false
	}
	raw, ok := p.config.Extensions[name]
	return raw, ok
}

Comment on lines +73 to +79
func (p *OtdfctlProfileStore) setRawExtension(name string, raw json.RawMessage) error {
if p.config.Extensions == nil {
p.config.Extensions = make(map[string]json.RawMessage)
}
p.config.Extensions[name] = raw
return p.store.Save()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

Add a nil check for p and p.config to prevent potential nil pointer dereference panics if the method is called on a nil profile store receiver or if the config is not initialized.

Suggested change
func (p *OtdfctlProfileStore) setRawExtension(name string, raw json.RawMessage) error {
if p.config.Extensions == nil {
p.config.Extensions = make(map[string]json.RawMessage)
}
p.config.Extensions[name] = raw
return p.store.Save()
}
func (p *OtdfctlProfileStore) setRawExtension(name string, raw json.RawMessage) error {
if p == nil || p.config == nil {
return ErrProfileConfigEmpty
}
if p.config.Extensions == nil {
p.config.Extensions = make(map[string]json.RawMessage)
}
p.config.Extensions[name] = raw
return p.store.Save()
}

Comment on lines +3 to +9
import (
"context"

"github.com/opentdf/platform/otdfctl/pkg/profiles"
"github.com/opentdf/platform/sdk"
"golang.org/x/oauth2"
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Import the sync package to support thread-safe access to the global authProviders map.

Suggested change
import (
"context"
"github.com/opentdf/platform/otdfctl/pkg/profiles"
"github.com/opentdf/platform/sdk"
"golang.org/x/oauth2"
)
import (
"context"
"sync"
"github.com/opentdf/platform/otdfctl/pkg/profiles"
"github.com/opentdf/platform/sdk"
"golang.org/x/oauth2"
)

Comment on lines +23 to +40
// authProviders maps an auth type to its provider. Registration is init-time and
// single-threaded, so no locking is required.
var authProviders = map[string]Provider{}

// RegisterProvider registers (or replaces) the provider for an auth type.
// Extending projects call this from init().
func RegisterProvider(authType string, provider Provider) {
authProviders[authType] = provider
}

// lookupProvider returns the provider for authType, or ErrInvalidAuthType if none.
func lookupProvider(authType string) (Provider, error) {
provider, ok := authProviders[authType]
if !ok {
return nil, ErrInvalidAuthType
}
return provider, nil
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

Use a sync.RWMutex to protect the global authProviders map from concurrent read/write race conditions and potential panics if providers are registered or looked up concurrently at runtime.

var (
	authProvidersMu sync.RWMutex
	authProviders   = map[string]Provider{}
)

// RegisterProvider registers (or replaces) the provider for an auth type.
// Extending projects call this from init().
func RegisterProvider(authType string, provider Provider) {
	authProvidersMu.Lock()
	defer authProvidersMu.Unlock()
	authProviders[authType] = provider
}

// lookupProvider returns the provider for authType, or ErrInvalidAuthType if none.
func lookupProvider(authType string) (Provider, error) {
	authProvidersMu.RLock()
	defer authProvidersMu.RUnlock()
	provider, ok := authProviders[authType]
	if !ok {
		return nil, ErrInvalidAuthType
	}
	return provider, nil
}

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor
Benchmark results, click to expand

Benchmark authorization.GetDecisions Results:

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

Benchmark authorization.v2.GetMultiResourceDecision Results:

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

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 477.702603ms
Throughput 209.34 requests/second

TDF3 Benchmark Results:

Metric Value
Total Requests 5000
Successful Requests 5000
Failed Requests 0
Concurrent Requests 50
Total Time 45.524630643s
Average Latency 453.455329ms
Throughput 109.83 requests/second

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

⚠️ Govulncheck found vulnerabilities ⚠️

The following modules have known vulnerabilities:

  • examples
  • otdfctl
  • sdk
  • service
  • lib/fixtures
  • tests-bdd

See the workflow run for details.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant