Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,8 @@ TINYAUTH_OIDC_CLIENTS_name_CLIENTSECRET=
TINYAUTH_OIDC_CLIENTS_name_CLIENTSECRETFILE=
# List of trusted redirect URIs.
TINYAUTH_OIDC_CLIENTS_name_TRUSTEDREDIRECTURIS=
# Skip the consent screen for this trusted OIDC client.
TINYAUTH_OIDC_CLIENTS_name_TRUSTED=false
# Client name in UI.
TINYAUTH_OIDC_CLIENTS_name_NAME=

Expand Down Expand Up @@ -222,8 +224,10 @@ TINYAUTH_LDAP_BINDPASSWORDFILE=
TINYAUTH_LDAP_BASEDN=
# Allow insecure LDAP connections.
TINYAUTH_LDAP_INSECURE=false
# LDAP search filter.
# LDAP user search filter. Use %s as the username placeholder.
TINYAUTH_LDAP_SEARCHFILTER="(uid=%s)"
# LDAP group search filter. Use %s as the user DN placeholder.
TINYAUTH_LDAP_GROUPSEARCHFILTER="(&(objectclass=groupOfUniqueNames)(uniquemember=%s))"
# Certificate for mTLS authentication.
TINYAUTH_LDAP_AUTHCERT=
# Certificate key for mTLS authentication.
Expand Down
8 changes: 8 additions & 0 deletions internal/controller/oidc_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,14 @@ func (controller *OIDCController) skipConsent(c *gin.Context) {
return
}

client, ok := controller.oidc.GetClient(authorizeReq.ClientID)
if ok && client.Trusted {
c.JSON(200, SkipConsentResponse{
SkipConsent: true,
})
return
}

consent, err := controller.oidc.GetOIDCConsent(c, userContext.GetUsername(), authorizeReq.ClientID)

if err != nil || consent == nil {
Expand Down
23 changes: 23 additions & 0 deletions internal/controller/oidc_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,29 @@ func TestOIDCController(t *testing.T) {
assert.False(t, res.SkipConsent)
},
},
{
description: "Skip consent returns true for a trusted client without prior consent",
middlewares: []gin.HandlerFunc{authedUser},
run: func(t *testing.T, router *gin.Engine, recorder *httptest.ResponseRecorder) {
require.NoError(t, store.DeleteOIDCConsentByClientID(ctx, "trusted-client-id"))

ticket := oidcService.CreateAuthorizeRequestTicket(service.AuthorizeRequest{
Scope: "openid profile",
ResponseType: "code",
ClientID: "trusted-client-id",
RedirectURI: "https://trusted.example.com/callback",
})

req := httptest.NewRequest("GET", "/api/oidc/skip-consent?oidc_ticket="+url.QueryEscape(ticket), nil)
router.ServeHTTP(recorder, req)

assert.Equal(t, http.StatusOK, recorder.Code)

var res SkipConsentResponse
require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &res))
assert.True(t, res.SkipConsent)
},
},
{
description: "Skip consent returns false when a new scope is requested",
middlewares: []gin.HandlerFunc{authedUser},
Expand Down
29 changes: 16 additions & 13 deletions internal/model/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,10 @@ func NewDefaultConfiguration(runtimeEnv RuntimeEnv) *Config {
WarningsEnabled: true,
},
LDAP: LDAPConfig{
Insecure: false,
SearchFilter: "(uid=%s)",
GroupCacheTTL: 900, // 15 minutes
Insecure: false,
SearchFilter: "(uid=%s)",
GroupSearchFilter: "(&(objectclass=groupOfUniqueNames)(uniquemember=%s))",
GroupCacheTTL: 900, // 15 minutes
},
Log: LogConfig{
Level: "info",
Expand Down Expand Up @@ -209,16 +210,17 @@ type UIConfig struct {
}

type LDAPConfig struct {
Address string `description:"LDAP server address." yaml:"address,omitempty"`
BindDN string `description:"Bind DN for LDAP authentication." yaml:"bindDn,omitempty"`
BindPassword string `description:"Bind password for LDAP authentication." yaml:"bindPassword,omitempty"`
BindPasswordFile string `description:"Path to the Bind password." yaml:"bindPasswordFile,omitempty"`
BaseDN string `description:"Base DN for LDAP searches." yaml:"baseDn,omitempty"`
Insecure bool `description:"Allow insecure LDAP connections." yaml:"insecure,omitempty"`
SearchFilter string `description:"LDAP search filter." yaml:"searchFilter,omitempty"`
AuthCert string `description:"Certificate for mTLS authentication." yaml:"authCert,omitempty"`
AuthKey string `description:"Certificate key for mTLS authentication." yaml:"authKey,omitempty"`
GroupCacheTTL int `description:"Cache duration for LDAP group membership in seconds." yaml:"groupCacheTTL,omitempty"`
Address string `description:"LDAP server address." yaml:"address,omitempty"`
BindDN string `description:"Bind DN for LDAP authentication." yaml:"bindDn,omitempty"`
BindPassword string `description:"Bind password for LDAP authentication." yaml:"bindPassword,omitempty"`
BindPasswordFile string `description:"Path to the Bind password." yaml:"bindPasswordFile,omitempty"`
BaseDN string `description:"Base DN for LDAP searches." yaml:"baseDn,omitempty"`
Insecure bool `description:"Allow insecure LDAP connections." yaml:"insecure,omitempty"`
SearchFilter string `description:"LDAP user search filter. Use %s as the username placeholder." yaml:"searchFilter,omitempty"`
GroupSearchFilter string `description:"LDAP group search filter. Use %s as the user DN placeholder." yaml:"groupSearchFilter,omitempty"`
AuthCert string `description:"Certificate for mTLS authentication." yaml:"authCert,omitempty"`
AuthKey string `description:"Certificate key for mTLS authentication." yaml:"authKey,omitempty"`
GroupCacheTTL int `description:"Cache duration for LDAP group membership in seconds." yaml:"groupCacheTTL,omitempty"`
}

type LogConfig struct {
Expand Down Expand Up @@ -282,6 +284,7 @@ type OIDCClientConfig struct {
ClientSecret string `description:"OIDC client secret." yaml:"clientSecret,omitempty"`
ClientSecretFile string `description:"Path to the file containing the OIDC client secret." yaml:"clientSecretFile,omitempty"`
TrustedRedirectURIs []string `description:"List of trusted redirect URIs." yaml:"trustedRedirectUris,omitempty"`
Trusted bool `description:"Skip the consent screen for this trusted OIDC client." yaml:"trusted,omitempty"`
Name string `description:"Client name in UI." yaml:"name,omitempty"`
}

Expand Down
2 changes: 1 addition & 1 deletion internal/service/ldap_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ func (ldap *LdapService) GetUserGroups(userDN string) ([]string, error) {
searchRequest := ldapgo.NewSearchRequest(
ldap.config.LDAP.BaseDN,
ldapgo.ScopeWholeSubtree, ldapgo.NeverDerefAliases, 0, 0, false,
fmt.Sprintf("(&(objectclass=groupOfUniqueNames)(uniquemember=%s))", escapedUserDN),
fmt.Sprintf(ldap.config.LDAP.GroupSearchFilter, escapedUserDN),
[]string{"dn"},
nil,
)
Expand Down
7 changes: 7 additions & 0 deletions internal/test/test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ func CreateTestConfigs(t *testing.T) (model.Config, model.RuntimeConfig) {
TrustedRedirectURIs: []string{"https://test.example.com/callback"},
Name: "Test Client",
},
"trusted-test": {
ClientID: "trusted-client-id",
ClientSecret: "trusted-client-secret",
TrustedRedirectURIs: []string{"https://trusted.example.com/callback"},
Trusted: true,
Name: "Trusted Test Client",
},
},
PrivateKeyPath: filepath.Join(tempDir, "key.pem"),
PublicKeyPath: filepath.Join(tempDir, "key.pub"),
Expand Down