Files

114 lines
5.0 KiB
Go

package oidc
import (
"strconv"
"testing"
"time"
"github.com/ory/fosite"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/pocket-id/pocket-id/backend/internal/model"
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
"github.com/pocket-id/pocket-id/backend/internal/utils"
)
// Interface assertions
var (
_ fosite.Client = (*Client)(nil)
_ fosite.ResponseModeClient = (*Client)(nil)
_ fosite.ClientWithCustomTokenLifespans = (*Client)(nil)
_ fosite.ClientWithSecretRotation = (*Client)(nil)
)
// testClientCredentials returns credentials with a single never-expiring secret with the given value
func testClientCredentials(values ...string) model.OidcClientCredentials {
credentials := model.OidcClientCredentials{
Secrets: make([]model.OidcClientSecret, len(values)),
}
for i, value := range values {
credentials.Secrets[i] = model.OidcClientSecret{
ID: "secret-" + strconv.Itoa(i),
Algorithm: model.OidcClientSecretHashSHA256,
Hash: utils.CreateSha256Hash(value),
Prefix: value[:model.OidcClientSecretPrefixLength],
CreatedAt: datatype.DateTime(time.Now()),
}
}
return credentials
}
func TestClientGetEffectiveLifespan(t *testing.T) {
client := Client{OidcClient: model.OidcClient{
AccessTokenDurationMinutes: 2 * 60,
RefreshTokenDurationMinutes: 7 * 24 * 60,
}}
fallback := 13 * time.Minute
for _, test := range []struct {
name string
grantType fosite.GrantType
tokenType fosite.TokenType
want time.Duration
}{
{name: "authorization code access token", grantType: fosite.GrantTypeAuthorizationCode, tokenType: fosite.AccessToken, want: 2 * time.Hour},
{name: "authorization code refresh token", grantType: fosite.GrantTypeAuthorizationCode, tokenType: fosite.RefreshToken, want: 7 * 24 * time.Hour},
{name: "refresh grant access token", grantType: fosite.GrantTypeRefreshToken, tokenType: fosite.AccessToken, want: 2 * time.Hour},
{name: "refresh grant refresh token", grantType: fosite.GrantTypeRefreshToken, tokenType: fosite.RefreshToken, want: 7 * 24 * time.Hour},
{name: "device grant access token", grantType: fosite.GrantTypeDeviceCode, tokenType: fosite.AccessToken, want: 2 * time.Hour},
{name: "device grant refresh token", grantType: fosite.GrantTypeDeviceCode, tokenType: fosite.RefreshToken, want: 7 * 24 * time.Hour},
{name: "client credentials access token", grantType: fosite.GrantTypeClientCredentials, tokenType: fosite.AccessToken, want: 2 * time.Hour},
{name: "client credentials refresh token falls back", grantType: fosite.GrantTypeClientCredentials, tokenType: fosite.RefreshToken, want: fallback},
{name: "ID token falls back", grantType: fosite.GrantTypeAuthorizationCode, tokenType: fosite.IDToken, want: fallback},
{name: "unsupported grant falls back", grantType: fosite.GrantTypePassword, tokenType: fosite.AccessToken, want: fallback},
} {
t.Run(test.name, func(t *testing.T) {
require.Equal(t, test.want, client.GetEffectiveLifespan(test.grantType, test.tokenType, fallback))
})
}
client.AccessTokenDurationMinutes = 0
client.RefreshTokenDurationMinutes = model.MaxTokenDurationMinutes + 1
require.Equal(t, fallback, client.GetEffectiveLifespan(fosite.GrantTypeAuthorizationCode, fosite.AccessToken, fallback))
require.Equal(t, fallback, client.GetEffectiveLifespan(fosite.GrantTypeAuthorizationCode, fosite.RefreshToken, fallback))
}
func TestClientSecretHashes(t *testing.T) {
past := datatype.DateTime(time.Now().Add(-time.Hour))
future := datatype.DateTime(time.Now().Add(time.Hour))
older := datatype.DateTime(time.Now().Add(-48 * time.Hour))
newer := datatype.DateTime(time.Now().Add(-time.Minute))
t.Run("client without secrets", func(t *testing.T) {
client := Client{}
assert.Nil(t, client.GetHashedSecret())
assert.Empty(t, client.GetRotatedHashes())
})
t.Run("expired secrets are never returned", func(t *testing.T) {
client := Client{OidcClient: model.OidcClient{Credentials: model.OidcClientCredentials{
Secrets: []model.OidcClientSecret{
{ID: "expired", Algorithm: model.OidcClientSecretHashSHA256, Hash: "expired-hash", ExpiresAt: &past},
},
}}}
assert.Nil(t, client.GetHashedSecret())
assert.Empty(t, client.GetRotatedHashes())
})
t.Run("the most recent active secret comes first", func(t *testing.T) {
client := Client{OidcClient: model.OidcClient{Credentials: model.OidcClientCredentials{
Secrets: []model.OidcClientSecret{
{ID: "older", Algorithm: model.OidcClientSecretHashSHA256, Hash: "older-hash", CreatedAt: older},
{ID: "expired", Algorithm: model.OidcClientSecretHashSHA256, Hash: "expired-hash", CreatedAt: newer, ExpiresAt: &past},
{ID: "newer", Algorithm: model.OidcClientSecretHashSHA256, Hash: "newer-hash", CreatedAt: newer, ExpiresAt: &future},
},
}}}
assert.Equal(t, "sha256:newer-hash", string(client.GetHashedSecret()))
rotated := client.GetRotatedHashes()
require.Len(t, rotated, 1)
assert.Equal(t, "sha256:older-hash", string(rotated[0]))
})
}