Skip to content

Feat: Pull users' Profile Picture from the OIDC claims#2704

Open
Guibi1 wants to merge 3 commits into
opencloud-eu:mainfrom
Guibi1:oidc-profile-picture
Open

Feat: Pull users' Profile Picture from the OIDC claims#2704
Guibi1 wants to merge 3 commits into
opencloud-eu:mainfrom
Guibi1:oidc-profile-picture

Conversation

@Guibi1
Copy link
Copy Markdown

@Guibi1 Guibi1 commented May 2, 2026

Description

This PR adds two settings:

  • PROXY_OIDC_PROFILE_PICTURE_CLAIM: Sets which oidc claim to use as the profile picture.
  • PROXY_OIDC_PROFILE_PICTURE_DISABLE_LOCAL_CHANGES: Stops the user from updating their profile pictures by disabling the graph endpoint.
  • PROXY_AUTOPROVISION_CLAIM_PROFILE_PICTURE: Sets which oidc claim to use as the profile picture. An empty string disables the feature.

Related Issue

Motivation and Context

See linked issue.

How Has This Been Tested?

  • manual testing

Screenshots (if appropriate):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Technical debt
  • Tests only (no source changes)

Checklist:

  • Code changes
  • Unit tests added
  • Acceptance tests added
  • Documentation added

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds support in the proxy service to (a) sync a user’s profile photo from a configurable OIDC claim on login, and (b) optionally forbid local profile-photo updates via the Graph “me/photo/$value” endpoint for OIDC-authenticated requests.

Changes:

  • Add PROXY_OIDC_PROFILE_PICTURE_CLAIM to fetch a profile-photo URL from OIDC claims and sync it on new sessions.
  • Add PROXY_OIDC_PROFILE_PICTURE_DISABLE_LOCAL_CHANGES to block PUT/PATCH/DELETE to /graph/v1.0/me/photo/$value for OIDC-authenticated requests.
  • Introduce proxy wiring for internal Graph calls (service discovery + separate backend HTTP client).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
services/proxy/pkg/middleware/options.go Extends middleware options with backend HTTP client, internal service selector, and OIDC profile-picture config.
services/proxy/pkg/middleware/account_resolver.go Implements profile-picture fetch + Graph update on new session; optionally blocks local photo mutations.
services/proxy/pkg/config/defaults/defaultconfig.go Adds default values for the new oidc_profile_picture config section.
services/proxy/pkg/config/config.go Introduces OIDCProfilePicture config and env vars for claim + local-change disable.
services/proxy/pkg/command/server.go Wires new config into middleware and creates a dedicated backend HTTP client for internal calls.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread services/proxy/pkg/middleware/account_resolver.go
Comment thread services/proxy/pkg/middleware/account_resolver.go
Comment thread services/proxy/pkg/middleware/account_resolver.go Outdated
Comment on lines +425 to +429
if m.profilePictureClaim != "" && oidc.NewSessionFlagFromContext(ctx) {
if err := m.syncProfilePicture(ctx, req, user, token, claims); err != nil {
m.logger.Warn().Err(err).Str("userid", user.GetId().GetOpaqueId()).Msg("Failed to sync profile picture from OIDC claim")
}
}
Comment thread services/proxy/pkg/config/config.go Outdated
Comment thread services/proxy/pkg/command/server.go
@dschmidt
Copy link
Copy Markdown
Contributor

dschmidt commented May 8, 2026

First of all: Thanks for the effort, @Guibi1!

I think this might entangle two separate concerns too tightly:

  1. Use profile pictures from oidc claims
  2. Disable profile picture updates for the user

I have a feeling the proxy is not the right place to handle the second concern. To me the route handling is crossing service domains in an unfortunate way.
IMHO it's the graph service which should be responsible for ultimately deciding this - and the frontend should be made aware of this, to hide the upload option etc.

When moving this responsibility to the graph service, this leaves us with the question of how to update the profile picture from the proxy (handling it at all there seems to be the right call as the proxy deals with all claims anyway), as the Graph HTTP Api now rejects updates.

Two possibilities from my view:

  1. Emit an event in the proxy and let the graph service consume it. This is rather easy and auditable, but it loses user auth. Again two possibilities:
    1.1 attach the image data to the event, limited by event bus config and feels a bit awkward
    1.2 have no user auth for fetching the image (is this really needed in reality? I haven't seen this in the wild anyway)
  2. Add a gRPC interface to the graph service: less auditable, but we can pass on user auth or bigger image data

It might be easier to discuss separate concerns in separate PRs - unfortunately they are heavily influencing each other...

@Guibi1
Copy link
Copy Markdown
Author

Guibi1 commented May 12, 2026

Agreed! For simplicity (and because i really want this merged), I straight up removed the second concern.
However, using a gRPC interface sounds like the best option, as long as there is already one on the graph service, it would be weird to have one just for this.

@sonarqubecloud
Copy link
Copy Markdown

@dschmidt
Copy link
Copy Markdown
Contributor

afaik there is none - personally I don't think it's too bad adding it, we're not coming up with a new pattern in this code base.

But ultimately not up for me to decide.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.

Comment thread services/proxy/pkg/middleware/options.go Outdated
Comment thread services/proxy/pkg/middleware/account_resolver.go
Comment thread services/proxy/pkg/command/server.go Outdated
Comment thread services/proxy/pkg/config/config.go
Comment thread services/proxy/pkg/config/config.go
Comment on lines +175 to +184
parsedURL, err := url.Parse(pictureURL)
if err != nil {
return fmt.Errorf("invalid profile picture URL: %w", err)
}
if parsedURL.Scheme != "http" && parsedURL.Scheme != "https" {
return fmt.Errorf("unsupported profile picture URL scheme: %s", parsedURL.Scheme)
}
if parsedURL.Host == "" {
return fmt.Errorf("profile picture URL is missing a host")
}
Comment on lines +247 to +251
issuerURL, err := url.Parse(m.oidcIssuer)
if err != nil || issuerURL.Host == "" {
return false
}
return strings.EqualFold(issuerURL.Host, pictureURL.Host)
Comment on lines +296 to +300
backendHTTPClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: backendTLSConfig,
},
Timeout: time.Second * 10,
Comment on lines +404 to +408
if m.profilePictureClaim != "" && oidc.NewSessionFlagFromContext(ctx) {
if err := m.syncProfilePicture(ctx, req, user, token, claims); err != nil {
m.logger.Warn().Err(err).Str("userid", user.GetId().GetOpaqueId()).Msg("Failed to sync profile picture from OIDC claim")
}
}
@codacy-production
Copy link
Copy Markdown

codacy-production Bot commented May 12, 2026

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 53 complexity · 0 duplication

Metric Results
Complexity 53
Duplication 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

Copy link
Copy Markdown

@codacy-production codacy-production Bot left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR cannot be merged in its current state due to high-severity logic and security issues.

Critical Findings

  • Compilation Errors: The code references options.AutoProvisionClaims and cfg.OIDCProfilePicture, neither of which exists in the provided configuration or middleware structs.
  • Security Risk: The fetchProfilePicture function lacks host validation, exposing the system to SSRF (Server-Side Request Forgery) by allowing requests to arbitrary URLs provided in OIDC claims.
  • Requirement Gaps: The setting PROXY_OIDC_PROFILE_PICTURE_DISABLE_LOCAL_CHANGES promised in the description is not implemented in the code.
  • Performance Concerns: Synchronizing profile pictures is handled synchronously within the middleware, which will block the login flow and increase latency for users during their first request.

About this PR

  • The account resolver middleware has high complexity and significant new logic for fetching and updating profile pictures, yet no unit tests or integration tests have been provided. This is particularly concerning given the identified logic errors.
  • There is a significant naming mismatch between the PR description ('PROXY_OIDC_PROFILE_PICTURE_CLAIM') and the code ('PROXY_AUTOPROVISION_CLAIM_PROFILE_PICTURE'). Furthermore, the OIDCProfilePicture type used in options.go and server.go appears to be undefined or incorrectly replaced by a string field in the configuration diff.

Test suggestions

  • Missing: Successful profile picture sync on new session with valid claim and URL
  • Missing: Skip profile picture sync if the session is not a new OIDC session
  • Missing: Reject profile picture sync if the URL scheme is not http or https
  • Missing: Reject profile picture if the response size exceeds 10MB limit
  • Missing: Verify Authorization header is only attached to image fetch requests if the host matches the OIDC issuer
  • Missing: Verify Graph API update request includes the correct CS3 token and content type
  • Missing: Verify local changes are disabled when PROXY_OIDC_PROFILE_PICTURE_DISABLE_LOCAL_CHANGES is true
Prompt proposal for missing tests
Consider implementing these tests if applicable:
1. Missing: Successful profile picture sync on new session with valid claim and URL
2. Missing: Skip profile picture sync if the session is not a new OIDC session
3. Missing: Reject profile picture sync if the URL scheme is not http or https
4. Missing: Reject profile picture if the response size exceeds 10MB limit
5. Missing: Verify Authorization header is only attached to image fetch requests if the host matches the OIDC issuer
6. Missing: Verify Graph API update request includes the correct CS3 token and content type
7. Missing: Verify local changes are disabled when PROXY_OIDC_PROFILE_PICTURE_DISABLE_LOCAL_CHANGES is true

TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback

client = http.DefaultClient
}

request, err := http.NewRequestWithContext(ctx, http.MethodGet, pictureURL.String(), nil)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 HIGH RISK

Fetching the profile picture from an arbitrary URL provided in OIDC claims poses a SSRF risk. You should validate that the URL's host matches the OIDC issuer or an allowed list of trusted domains to prevent internal network scanning.

Comment thread services/proxy/pkg/middleware/account_resolver.go
Comment thread services/proxy/pkg/command/server.go Outdated
Comment thread services/proxy/pkg/middleware/account_resolver.go Outdated
Comment thread services/proxy/pkg/config/config.go

const (
graphServiceName = "eu.opencloud.web.graph"
maxProfilePhotoBytes = 10 << 20
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 MEDIUM RISK

Suggestion: The 10MB limit for profile photos is excessive for a synchronous automated sync process. Consider reducing this to 1-2MB to prevent high memory usage and mitigate resource exhaustion risks during peak login times.

}

if m.profilePictureClaim != "" && oidc.NewSessionFlagFromContext(ctx) {
if err := m.syncProfilePicture(ctx, req, user, token, claims); err != nil {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 MEDIUM RISK

Suggestion: Syncing the profile picture synchronously blocks the request flow and increases latency for the initial session request. Consider running this in a background goroutine using a detached context (e.g., context.WithoutCancel).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This might be a good idea.

Comment thread services/proxy/pkg/command/server.go
if len(data) > maxProfilePhotoBytes {
return nil, fmt.Errorf("profile picture exceeds %d bytes", maxProfilePhotoBytes)
}
contentType := http.DetectContentType(data)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚪ LOW RISK

Suggestion: The content-type is detected twice for the same image data. Optimize this by returning the detected content-type from fetchProfilePicture and passing it to updateGraphProfilePhoto.

Copy link
Copy Markdown

@codacy-production codacy-production Bot left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces OIDC-based profile picture synchronization and the ability to disable local profile updates. While the implementation aligns with the stated goals via new configuration flags, there is a total lack of automated test coverage for these features.

Codacy analysis indicates the PR is technically up to standards regarding code style; however, the account_resolver.go and options.go files have seen a large increase in complexity (101 and 61 respectively) without any new tests to mitigate regression risk. It is recommended to address the missing test scenarios before merging.

About this PR

  • This PR lacks unit and acceptance tests for the core logic. Specifically, the mapping of the OIDC claim to the user profile and the logic to block graph API updates remain unverified. Automated tests are required to ensure the OIDC claim parsing handles various formats (URL vs. binary) correctly.
  • Multiple files (account_resolver.go, options.go, server.go, defaultconfig.go) have undergone significant complexity increases without being covered by tests. This creates a high risk of long-term maintenance issues and hidden bugs in the proxy middleware.

Test suggestions

  • Verify that the profile picture is correctly mapped from the OIDC claim specified in PROXY_OIDC_PROFILE_PICTURE_CLAIM.
  • Verify that attempts to update the profile picture via the graph endpoint return an error or are blocked when PROXY_OIDC_PROFILE_PICTURE_DISABLE_LOCAL_CHANGES is true.
  • Unit tests for new logic in services/proxy/pkg/middleware/account_resolver.go (Complexity: 101, Coverage: 0%).
  • Unit tests for new logic in services/proxy/pkg/middleware/options.go (Complexity: 61, Coverage: 0%).
  • Unit tests for new logic in services/proxy/pkg/command/server.go (Complexity: 28, Coverage: 0%).
  • Unit tests for new logic in services/proxy/pkg/config/defaults/defaultconfig.go (Complexity: 27, Coverage: 0%).
Prompt proposal for missing tests
Consider implementing these tests if applicable:
1. Verify that the profile picture is correctly mapped from the OIDC claim specified in PROXY_OIDC_PROFILE_PICTURE_CLAIM.
2. Verify that attempts to update the profile picture via the graph endpoint return an error or are blocked when PROXY_OIDC_PROFILE_PICTURE_DISABLE_LOCAL_CHANGES is true.
3. Unit tests for new logic in services/proxy/pkg/middleware/account_resolver.go (Complexity: 101, Coverage: 0%).
4. Unit tests for new logic in services/proxy/pkg/middleware/options.go (Complexity: 61, Coverage: 0%).
5. Unit tests for new logic in services/proxy/pkg/command/server.go (Complexity: 28, Coverage: 0%).
6. Unit tests for new logic in services/proxy/pkg/config/defaults/defaultconfig.go (Complexity: 27, Coverage: 0%).

TIP Improve review quality by adding custom instructions
TIP How was this review? Give us feedback

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FR] Tell Opencloud OIDC where to find the Profile Picture

3 participants