Skip to content

Commit

Permalink
Add SourceHut (sr.ht) provider
Browse files Browse the repository at this point in the history
Co-authored-by: Jan Larwig <[email protected]>
  • Loading branch information
bitfehler and tuunit committed Feb 9, 2024
1 parent 84e1cc2 commit bb0a911
Show file tree
Hide file tree
Showing 6 changed files with 218 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
- [#2357](https://github.com/oauth2-proxy/oauth2-proxy/pull/2357) Update ojg to latest release (@bitfehler)
- [#1922](https://github.com/oauth2-proxy/oauth2-proxy/pull/1922) Added support for env variables in the alpha struct (@hevans-dglcom)
- [#2235](https://github.com/oauth2-proxy/oauth2-proxy/pull/2235) Bump golang to 1.21 and min allowed version to 1.20 (@tuunit)
- [#2359](https://github.com/oauth2-proxy/oauth2-proxy/pull/2359) Add SourceHut (sr.ht) provider(@bitfehler)

# V7.5.1

Expand Down
25 changes: 25 additions & 0 deletions docs/docs/configuration/providers/sourcehut.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
---
id: sourcehut
title: SourceHut
---

1. Create a new OAuth client: https://meta.sr.ht/oauth2
2. Under `Redirection URI` enter the correct URL, i.e.
`https://internal.yourcompany.com/oauth2/callback`

To use the provider, start with `--provider=sourcehut`.

If you are hosting your own SourceHut instance, make sure you set the following
to the appropriate URLs:

```shell
--login-url="https://<meta.your.instance>/oauth2/authorize"
--redeem-url="https://<meta.your.instance>/oauth2/access-token"
--profile-url="https://<meta.your.instance>/query"
--validate-url="https://<meta.your.instance>/profile"
```

The default configuration allows everyone with an account to authenticate.
Restricting access is currently only supported by
[email](#email-authentication).

3 changes: 3 additions & 0 deletions pkg/apis/options/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ const (

// OIDCProvider is the provider type for OIDC
OIDCProvider ProviderType = "oidc"

// SourceHutProvider is the provider type for SourceHut
SourceHutProvider ProviderType = "sourcehut"
)

type KeycloakOptions struct {
Expand Down
5 changes: 4 additions & 1 deletion providers/providers.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ func NewProvider(providerConfig options.Provider) (Provider, error) {
return NewNextcloudProvider(providerData), nil
case options.OIDCProvider:
return NewOIDCProvider(providerData, providerConfig.OIDCConfig), nil
case options.SourceHutProvider:
return NewSourceHutProvider(providerData), nil
default:
return nil, fmt.Errorf("unknown provider type %q", providerConfig.Type)
}
Expand Down Expand Up @@ -179,7 +181,8 @@ func parseCodeChallengeMethod(providerConfig options.Provider) string {
func providerRequiresOIDCProviderVerifier(providerType options.ProviderType) (bool, error) {
switch providerType {
case options.BitbucketProvider, options.DigitalOceanProvider, options.FacebookProvider, options.GitHubProvider,
options.GoogleProvider, options.KeycloakProvider, options.LinkedInProvider, options.LoginGovProvider, options.NextCloudProvider:
options.GoogleProvider, options.KeycloakProvider, options.LinkedInProvider, options.LoginGovProvider,
options.NextCloudProvider, options.SourceHutProvider:
return false, nil
case options.ADFSProvider, options.AzureProvider, options.GitLabProvider, options.KeycloakOIDCProvider, options.OIDCProvider:
return true, nil
Expand Down
108 changes: 108 additions & 0 deletions providers/srht.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package providers

import (
"bytes"
"context"
"fmt"
"net/url"

"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
)

type SourceHutProvider struct {
*ProviderData
}

var _ Provider = (*SourceHutProvider)(nil)

const (
SourceHutProviderName = "SourceHut"
SourceHutDefaultScope = "meta.sr.ht/PROFILE:RO"
)

var (
// Default Login URL for SourceHut.
// Pre-parsed URL of https://meta.sr.ht/oauth2/authorize.
SourceHutDefaultLoginURL = &url.URL{
Scheme: "https",
Host: "meta.sr.ht",
Path: "/oauth2/authorize",
}

// Default Redeem URL for SourceHut.
// Pre-parsed URL of https://meta.sr.ht/oauth2/access-token.
SourceHutDefaultRedeemURL = &url.URL{
Scheme: "https",
Host: "meta.sr.ht",
Path: "/oauth2/access-token",
}

// Default Profile URL for SourceHut.
// Pre-parsed URL of https://meta.sr.ht/query.
SourceHutDefaultProfileURL = &url.URL{
Scheme: "https",
Host: "meta.sr.ht",
Path: "/query",
}

// Default Validation URL for SourceHut.
// Pre-parsed URL of https://meta.sr.ht/profile.
SourceHutDefaultValidateURL = &url.URL{
Scheme: "https",
Host: "meta.sr.ht",
Path: "/profile",
}
)

// NewSourceHutProvider creates a SourceHutProvider using the passed ProviderData
func NewSourceHutProvider(p *ProviderData) *SourceHutProvider {
p.setProviderDefaults(providerDefaults{
name: SourceHutProviderName,
loginURL: SourceHutDefaultLoginURL,
redeemURL: SourceHutDefaultRedeemURL,
profileURL: SourceHutDefaultProfileURL,
validateURL: SourceHutDefaultValidateURL,
scope: SourceHutDefaultScope,
})

return &SourceHutProvider{ProviderData: p}
}

// EnrichSession uses the SourceHut userinfo endpoint to populate the session's
// email and username.
func (p *SourceHutProvider) EnrichSession(ctx context.Context, s *sessions.SessionState) error {
json, err := requests.New(p.ProfileURL.String()).
WithContext(ctx).
WithMethod("POST").
SetHeader("Content-Type", "application/json").
SetHeader("Authorization", "Bearer "+s.AccessToken).
WithBody(bytes.NewBufferString(`{"query": "{ me { username, email } }"}`)).
Do().
UnmarshalSimpleJSON()
if err != nil {
logger.Errorf("failed making request %v", err)
return err
}

email, err := json.GetPath("data", "me", "email").String()
if err != nil {
return fmt.Errorf("unable to extract email from userinfo endpoint: %v", err)
}
s.Email = email

username, err := json.GetPath("data", "me", "username").String()
if err != nil {
return fmt.Errorf("unable to extract username from userinfo endpoint: %v", err)
}
s.PreferredUsername = username
s.User = username

return nil
}

// ValidateSession validates the AccessToken
func (p *SourceHutProvider) ValidateSession(ctx context.Context, s *sessions.SessionState) bool {
return validateToken(ctx, p, s.AccessToken, makeOIDCHeader(s.AccessToken))
}
77 changes: 77 additions & 0 deletions providers/srht_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package providers

import (
"context"
"net/http"
"net/http/httptest"
"net/url"
"testing"

"github.com/stretchr/testify/assert"
)

func testSourceHutProvider(hostname string) *SourceHutProvider {
p := NewSourceHutProvider(
&ProviderData{
ProviderName: "SourceHut",
LoginURL: &url.URL{},
RedeemURL: &url.URL{},
ProfileURL: &url.URL{},
ValidateURL: &url.URL{},
Scope: ""},
)
p.ProviderName = "SourceHut"

if hostname != "" {
updateURL(p.Data().LoginURL, hostname)
updateURL(p.Data().RedeemURL, hostname)
updateURL(p.Data().ProfileURL, hostname)
updateURL(p.Data().ValidateURL, hostname)
}
return p
}

func testSourceHutBackend(payloads map[string][]string) *httptest.Server {
return httptest.NewServer(http.HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
index := 0
payload, ok := payloads[r.URL.Path]
if !ok {
w.WriteHeader(404)
} else if payload[index] == "" {
w.WriteHeader(204)
} else {
w.WriteHeader(200)
w.Write([]byte(payload[index]))
}
}))
}

func TestSourceHutProvider_ValidateSessionWithBaseUrl(t *testing.T) {
b := testSourceHutBackend(map[string][]string{})
defer b.Close()

bURL, _ := url.Parse(b.URL)
p := testSourceHutProvider(bURL.Host)

session := CreateAuthorizedSession()

valid := p.ValidateSession(context.Background(), session)
assert.False(t, valid)
}

func TestSourceHutProvider_ValidateSessionWithUserEmails(t *testing.T) {
b := testSourceHutBackend(map[string][]string{
"/query": {`{"data":{"me":{"username":"bitfehler","email":"[email protected]"}}}`},
"/profile": {`ok`},
})
defer b.Close()

bURL, _ := url.Parse(b.URL)
p := testSourceHutProvider(bURL.Host)

session := CreateAuthorizedSession()

valid := p.ValidateSession(context.Background(), session)
assert.True(t, valid)
}

0 comments on commit bb0a911

Please sign in to comment.