mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-12 00:34:13 +01:00
Vision: Allow use of configured service key for API authentication #5299
Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
@@ -19,6 +19,7 @@ var (
|
||||
CachePath = ""
|
||||
ModelsPath = ""
|
||||
DownloadUrl = ""
|
||||
ServiceApi = false
|
||||
ServiceUri = ""
|
||||
ServiceKey = ""
|
||||
ServiceTimeout = 10 * time.Minute
|
||||
|
||||
@@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/ai/vision"
|
||||
"github.com/photoprism/photoprism/internal/auth/acl"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
@@ -33,9 +34,20 @@ func AuthAny(c *gin.Context, resource acl.Resource, perms acl.Permissions) (s *e
|
||||
// Disable response caching.
|
||||
c.Header(header.CacheControl, header.CacheControlNoStore)
|
||||
|
||||
// Allow requests based on an access token for specific resources.
|
||||
switch resource {
|
||||
case acl.ResourceVision:
|
||||
if perms.Contains(acl.ActionUse) && vision.ServiceApi && vision.ServiceKey != "" && vision.ServiceKey == authToken {
|
||||
s = entity.NewSessionFromToken(c, authToken, acl.ResourceVision.String(), "service-key")
|
||||
event.AuditInfo([]string{clientIp, "%s", "%s %s as %s", status.Granted}, s.RefID, perms.First(), string(resource), s.GetClientRole().String())
|
||||
return s
|
||||
}
|
||||
}
|
||||
|
||||
// Find active session to perform authorization check or deny if no session was found.
|
||||
if s = Session(clientIp, authToken); s == nil {
|
||||
if s = authAnyJWT(c, clientIp, authToken, resource, perms); s != nil {
|
||||
event.AuditInfo([]string{clientIp, "session %s", "%s %s as %s", status.Granted}, s.RefID, perms.First(), string(resource), s.GetClientRole().String())
|
||||
return s
|
||||
}
|
||||
event.AuditWarn([]string{clientIp, "%s %s without authentication", status.Denied}, perms.String(), string(resource))
|
||||
|
||||
@@ -45,7 +45,7 @@ func TestAuthAnyJWT(t *testing.T) {
|
||||
assert.Contains(t, session.AuthScope, "cluster")
|
||||
assert.Equal(t, spec.Issuer, session.AuthIssuer)
|
||||
assert.Equal(t, authn.MethodJWT.String(), session.AuthMethod)
|
||||
assert.Equal(t, authn.ProviderClient.String(), session.AuthProvider)
|
||||
assert.Equal(t, authn.ProviderAccessToken.String(), session.AuthProvider)
|
||||
assert.Equal(t, authn.GrantJwtBearer.String(), session.GrantType)
|
||||
assert.Equal(t, "192.0.2.10", session.ClientIP)
|
||||
assert.Equal(t, "PhotoPrism Portal/1.0", session.UserAgent)
|
||||
|
||||
@@ -11,12 +11,14 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/ai/vision"
|
||||
"github.com/photoprism/photoprism/internal/auth/acl"
|
||||
clusterjwt "github.com/photoprism/photoprism/internal/auth/jwt"
|
||||
"github.com/photoprism/photoprism/internal/auth/session"
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||
"github.com/photoprism/photoprism/internal/service/cluster"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/http/header"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
@@ -146,6 +148,44 @@ func TestAuthToken(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthAnyVisionServiceKey(t *testing.T) {
|
||||
origAPI := vision.ServiceApi
|
||||
origKey := vision.ServiceKey
|
||||
defer func() {
|
||||
vision.ServiceApi = origAPI
|
||||
vision.ServiceKey = origKey
|
||||
}()
|
||||
|
||||
vision.ServiceApi = true
|
||||
vision.ServiceKey = "vision-service-key-abc123"
|
||||
|
||||
gin.SetMode(gin.TestMode)
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/vision/labels", nil)
|
||||
header.SetAuthorization(req, vision.ServiceKey)
|
||||
req.RemoteAddr = "198.51.100.24:1234"
|
||||
req.Header.Set(header.UserAgent, "VisionClient/1.0")
|
||||
c.Request = req
|
||||
|
||||
s := AuthAny(c, acl.ResourceVision, acl.Permissions{acl.ActionUse})
|
||||
require.NotNil(t, s)
|
||||
assert.False(t, s.Abort(c))
|
||||
assert.Equal(t, http.StatusOK, s.HttpStatus())
|
||||
assert.Equal(t, vision.ServiceKey, s.AuthToken())
|
||||
assert.Equal(t, rnd.SessionID(vision.ServiceKey), s.ID)
|
||||
assert.Equal(t, acl.ResourceVision.String(), s.Scope())
|
||||
assert.Equal(t, authn.GrantToken, s.GetGrantType())
|
||||
assert.Equal(t, authn.ProviderAccessToken, s.GetProvider())
|
||||
assert.Equal(t, authn.MethodDefault, s.GetMethod())
|
||||
assert.Equal(t, header.ClientIP(c), s.ClientIP)
|
||||
assert.Equal(t, req.UserAgent(), s.UserAgent)
|
||||
assert.True(t, s.IsClient())
|
||||
assert.Equal(t, acl.RoleClient, s.GetClientRole())
|
||||
assert.EqualValues(t, 60, s.SessTimeout)
|
||||
assert.True(t, rnd.IsRefID(s.RefID))
|
||||
}
|
||||
|
||||
func TestAuthAnyPortalJWT(t *testing.T) {
|
||||
fx := newPortalJWTFixture(t, "ok")
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
"",
|
||||
"cli",
|
||||
"implicit",
|
||||
"token",
|
||||
"session",
|
||||
"password",
|
||||
"client_credentials",
|
||||
@@ -57,6 +58,7 @@
|
||||
"GrantUndefined",
|
||||
"GrantCLI",
|
||||
"GrantImplicit",
|
||||
"GrantToken",
|
||||
"GrantSession",
|
||||
"GrantPassword",
|
||||
"GrantClientCredentials",
|
||||
@@ -2358,6 +2360,9 @@
|
||||
"Role": {
|
||||
"type": "string"
|
||||
},
|
||||
"Scope": {
|
||||
"type": "string"
|
||||
},
|
||||
"Settings": {
|
||||
"$ref": "#/definitions/entity.UserSettings"
|
||||
},
|
||||
@@ -3278,6 +3283,9 @@
|
||||
"Role": {
|
||||
"type": "string"
|
||||
},
|
||||
"Scope": {
|
||||
"type": "string"
|
||||
},
|
||||
"SuperAdmin": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package acl
|
||||
|
||||
// Any matches everything.
|
||||
const Any = "*"
|
||||
|
||||
// RoleAliasNone is a more explicit, user-friendly alias for RoleNone.
|
||||
const RoleAliasNone = "none"
|
||||
|
||||
|
||||
@@ -10,11 +10,6 @@ func (p Permission) String() string {
|
||||
return strings.ReplaceAll(string(p), "_", " ")
|
||||
}
|
||||
|
||||
// LogId returns an identifier string for use in log messages.
|
||||
func (p Permission) LogId() string {
|
||||
return p.String()
|
||||
}
|
||||
|
||||
// Equal checks if the type matches.
|
||||
func (p Permission) Equal(s string) bool {
|
||||
return strings.EqualFold(s, p.String())
|
||||
|
||||
@@ -6,30 +6,6 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPermissions_String(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
perms := Permissions{}
|
||||
assert.Equal(t, "", perms.String())
|
||||
})
|
||||
t.Run("FullAccess", func(t *testing.T) {
|
||||
perms := Permissions{FullAccess}
|
||||
assert.Equal(t, "full access", perms.String())
|
||||
})
|
||||
t.Run("ManageUploadAll", func(t *testing.T) {
|
||||
perms := Permissions{ActionManage, ActionUpload, AccessAll}
|
||||
assert.Equal(t, "manage, upload, access all", perms.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestPermission_LogId(t *testing.T) {
|
||||
t.Run("FullAccess", func(t *testing.T) {
|
||||
assert.Equal(t, "full access", FullAccess.LogId())
|
||||
})
|
||||
t.Run("ActionUpload", func(t *testing.T) {
|
||||
assert.Equal(t, "upload", ActionUpload.LogId())
|
||||
})
|
||||
}
|
||||
|
||||
func TestPermission_Equal(t *testing.T) {
|
||||
t.Run("True", func(t *testing.T) {
|
||||
assert.True(t, FullAccess.Equal("full access"))
|
||||
|
||||
@@ -15,3 +15,30 @@ func (perm Permissions) String() string {
|
||||
|
||||
return strings.Join(s, ", ")
|
||||
}
|
||||
|
||||
// First returns the first permission as a string. When the slice is empty it defaults to ActionUse.
|
||||
func (perm Permissions) First() string {
|
||||
if len(perm) == 0 {
|
||||
return ActionUse.String()
|
||||
}
|
||||
|
||||
return perm[0].String()
|
||||
}
|
||||
|
||||
// Contains reports whether the specified permission or wildcard is present in this set.
|
||||
func (perm Permissions) Contains(p Permission) bool {
|
||||
if len(perm) == 0 || p == "" {
|
||||
return false
|
||||
} else if p == Any {
|
||||
return true
|
||||
}
|
||||
|
||||
// Find matches.
|
||||
for i := range perm {
|
||||
if p == perm[i] || perm[i] == Any {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
53
internal/auth/acl/permissions_test.go
Normal file
53
internal/auth/acl/permissions_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package acl
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPermissions_String(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
perms := Permissions{}
|
||||
assert.Equal(t, "", perms.String())
|
||||
})
|
||||
t.Run("FullAccess", func(t *testing.T) {
|
||||
perms := Permissions{FullAccess}
|
||||
assert.Equal(t, "full access", perms.String())
|
||||
})
|
||||
t.Run("ManageUploadAll", func(t *testing.T) {
|
||||
perms := Permissions{ActionManage, ActionUpload, AccessAll}
|
||||
assert.Equal(t, "manage, upload, access all", perms.String())
|
||||
})
|
||||
}
|
||||
|
||||
func TestPermissions_First(t *testing.T) {
|
||||
t.Run("Default", func(t *testing.T) {
|
||||
perms := Permissions{}
|
||||
assert.Equal(t, ActionUse.String(), perms.First())
|
||||
})
|
||||
t.Run("Explicit", func(t *testing.T) {
|
||||
perms := Permissions{ActionManage, ActionUpload}
|
||||
assert.Equal(t, ActionManage.String(), perms.First())
|
||||
})
|
||||
}
|
||||
|
||||
func TestPermissions_Contains(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
perms := Permissions{}
|
||||
assert.False(t, perms.Contains(ActionUse))
|
||||
})
|
||||
t.Run("ExactMatch", func(t *testing.T) {
|
||||
perms := Permissions{ActionManage, ActionUpload}
|
||||
assert.True(t, perms.Contains(ActionUpload))
|
||||
assert.False(t, perms.Contains(ActionDelete))
|
||||
})
|
||||
t.Run("WildcardInSet", func(t *testing.T) {
|
||||
perms := Permissions{Any}
|
||||
assert.True(t, perms.Contains(ActionDelete))
|
||||
})
|
||||
t.Run("WildcardRequested", func(t *testing.T) {
|
||||
perms := Permissions{ActionManage}
|
||||
assert.True(t, perms.Contains(Any))
|
||||
})
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package acl
|
||||
|
||||
// ScopeDescriptions maps supported authorization scopes to human-readable summaries for CLI help and docs.
|
||||
var ScopeDescriptions = map[string]string{
|
||||
"*": "Wildcard granting full access to every resource and permission.",
|
||||
Any: "Wildcard granting full access to every resource and permission.",
|
||||
ScopeRead.String(): "Read-only access across all resources (search, view, download).",
|
||||
ScopeWrite.String(): "Write access across all resources (create, update, delete) without implicit read permissions.",
|
||||
ResourceFiles.String(): "Full access to original and derived files on disk.",
|
||||
|
||||
@@ -318,6 +318,7 @@ func (c *Config) Propagate() {
|
||||
// Configure computer vision package.
|
||||
vision.SetCachePath(c.CachePath())
|
||||
vision.SetModelsPath(c.ModelsPath())
|
||||
vision.ServiceApi = c.VisionApi()
|
||||
vision.ServiceUri = c.VisionUri()
|
||||
vision.ServiceKey = c.VisionKey()
|
||||
vision.DownloadUrl = c.DownloadUrl()
|
||||
|
||||
@@ -92,6 +92,50 @@ func NewSession(expiresIn, timeout int64) (sess *Session) {
|
||||
return sess
|
||||
}
|
||||
|
||||
// NewSessionFromToken creates a transient access-token session backed by the caller's bearer token.
|
||||
// It copies scope, client metadata, and request context so service-key and API token flows can reuse
|
||||
// the existing authorization path without persisting state.
|
||||
func NewSessionFromToken(c *gin.Context, token, scope, refId string) *Session {
|
||||
if token == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// "vision-api"
|
||||
if !rnd.IsRefID(refId) {
|
||||
refId = rnd.AuthTokenID("key")
|
||||
}
|
||||
|
||||
// Create new session
|
||||
sess := &Session{
|
||||
Status: http.StatusOK,
|
||||
RefID: refId,
|
||||
}
|
||||
|
||||
// Determine token string.
|
||||
sess.SetAuthToken(token)
|
||||
|
||||
// Set scope/claims metadata.
|
||||
sess.SetScope(scope)
|
||||
sess.SetGrantType(authn.GrantToken)
|
||||
sess.SetMethod(authn.MethodDefault)
|
||||
sess.SetProvider(authn.ProviderAccessToken)
|
||||
sess.SetClientIP(header.ClientIP(c))
|
||||
sess.SetUserAgent(header.ClientUserAgent(c))
|
||||
|
||||
// Derive timestamps from JWT claims when available.
|
||||
now := time.Now().UTC()
|
||||
|
||||
sess.CreatedAt = now
|
||||
sess.UpdatedAt = now
|
||||
sess.LastActive = now.Unix()
|
||||
|
||||
// Expires in 60 seconds.
|
||||
sess.SetExpiresIn(60)
|
||||
sess.SetTimeout(60)
|
||||
|
||||
return sess
|
||||
}
|
||||
|
||||
// SessionStatusUnauthorized returns a session with status unauthorized (401).
|
||||
func SessionStatusUnauthorized() *Session {
|
||||
return &Session{Status: http.StatusUnauthorized}
|
||||
|
||||
@@ -47,13 +47,11 @@ func NewSessionFromJWT(c *gin.Context, jwt *JWT) *Session {
|
||||
sess.SetAuthToken(token)
|
||||
|
||||
// Set scope/claims metadata.
|
||||
if jwt.Scope != "" {
|
||||
sess.SetScope(jwt.Scope)
|
||||
}
|
||||
sess.SetScope(jwt.Scope)
|
||||
sess.SetAuthID(jwt.ID, jwt.Issuer)
|
||||
sess.SetGrantType(authn.GrantJwtBearer)
|
||||
sess.SetMethod(authn.MethodJWT)
|
||||
sess.SetProvider(authn.ProviderClient)
|
||||
sess.SetProvider(authn.ProviderAccessToken)
|
||||
sess.SetClientName(jwt.Subject)
|
||||
sess.SetClientIP(header.ClientIP(c))
|
||||
sess.SetUserAgent(header.ClientUserAgent(c))
|
||||
|
||||
@@ -48,7 +48,7 @@ func TestNewSessionFromJWT(t *testing.T) {
|
||||
assert.True(t, rnd.IsSessionID(sess.ID))
|
||||
assert.Equal(t, authn.GrantJwtBearer.String(), sess.GrantType)
|
||||
assert.Equal(t, authn.MethodJWT.String(), sess.AuthMethod)
|
||||
assert.Equal(t, authn.ProviderClient.String(), sess.AuthProvider)
|
||||
assert.Equal(t, authn.ProviderAccessToken.String(), sess.AuthProvider)
|
||||
assert.Equal(t, "portal:cs5cpu17n6gj2qo5", sess.GetClientName())
|
||||
assert.Equal(t, clean.Scope("cluster vision"), sess.AuthScope)
|
||||
assert.Equal(t, "portal:cbaa0276-07d3-43ac-b420-25e2601b0ad4", sess.AuthIssuer)
|
||||
|
||||
@@ -2,6 +2,7 @@ package entity
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@@ -62,6 +63,41 @@ func TestNewSession(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestNewSessionFromToken(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
|
||||
t.Run("EmptyToken", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
|
||||
assert.Nil(t, NewSessionFromToken(c, "", acl.ResourceVision.String(), "vision-api"))
|
||||
})
|
||||
t.Run("PopulatedSession", func(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(w)
|
||||
req := httptest.NewRequest(http.MethodPost, "/api/v1/vision/labels", nil)
|
||||
req.RemoteAddr = "198.51.100.42:8080"
|
||||
req.Header.Set(header.UserAgent, "VisionClient/1.0")
|
||||
c.Request = req
|
||||
|
||||
token := "vision-service-key-abc123"
|
||||
sess := NewSessionFromToken(c, token, acl.ResourceVision.String(), "vision-api")
|
||||
if assert.NotNil(t, sess) {
|
||||
assert.Equal(t, http.StatusOK, sess.HttpStatus())
|
||||
assert.Equal(t, token, sess.AuthToken())
|
||||
assert.Equal(t, rnd.SessionID(token), sess.ID)
|
||||
assert.Equal(t, acl.ResourceVision.String(), sess.Scope())
|
||||
assert.Equal(t, authn.GrantToken, sess.GetGrantType())
|
||||
assert.Equal(t, authn.MethodDefault, sess.GetMethod())
|
||||
assert.Equal(t, authn.ProviderAccessToken, sess.GetProvider())
|
||||
assert.Equal(t, header.ClientIP(c), sess.ClientIP)
|
||||
assert.Equal(t, req.UserAgent(), sess.UserAgent)
|
||||
assert.EqualValues(t, 60, sess.SessTimeout)
|
||||
assert.True(t, rnd.IsRefID(sess.RefID))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSession_SetData(t *testing.T) {
|
||||
t.Run("Nil", func(t *testing.T) {
|
||||
m := NewSession(unix.Day, unix.Hour*6)
|
||||
|
||||
@@ -15,6 +15,7 @@ const (
|
||||
GrantUndefined GrantType = ""
|
||||
GrantCLI GrantType = "cli"
|
||||
GrantImplicit GrantType = "implicit"
|
||||
GrantToken GrantType = "token"
|
||||
GrantSession GrantType = "session"
|
||||
GrantPassword GrantType = "password"
|
||||
GrantClientCredentials GrantType = "client_credentials"
|
||||
@@ -36,6 +37,8 @@ func Grant(s string) GrantType {
|
||||
return GrantCLI
|
||||
case "implicit":
|
||||
return GrantImplicit
|
||||
case "token":
|
||||
return GrantToken
|
||||
case "session":
|
||||
return GrantSession
|
||||
case "password", "passwd", "pass":
|
||||
@@ -66,6 +69,8 @@ func (t GrantType) Pretty() string {
|
||||
return "CLI"
|
||||
case GrantImplicit:
|
||||
return "Implicit"
|
||||
case GrantToken:
|
||||
return "Token"
|
||||
case GrantSession:
|
||||
return "Session"
|
||||
case GrantPassword:
|
||||
|
||||
@@ -11,6 +11,7 @@ func TestGrantType_String(t *testing.T) {
|
||||
assert.Equal(t, "client_credentials", GrantClientCredentials.String())
|
||||
assert.Equal(t, "session", GrantSession.String())
|
||||
assert.Equal(t, "password", GrantPassword.String())
|
||||
assert.Equal(t, "token", GrantToken.String())
|
||||
assert.Equal(t, "refresh_token", GrantRefreshToken.String())
|
||||
assert.Equal(t, "authorization_code", GrantAuthorizationCode.String())
|
||||
assert.Equal(t, "authorization_code", GrantType("Authorization Code ").String())
|
||||
@@ -22,6 +23,7 @@ func TestGrantType_Is(t *testing.T) {
|
||||
assert.Equal(t, true, GrantUndefined.Is(GrantUndefined))
|
||||
assert.Equal(t, true, GrantClientCredentials.Is(GrantClientCredentials))
|
||||
assert.Equal(t, true, GrantSession.Is(GrantSession))
|
||||
assert.Equal(t, true, GrantToken.Is(GrantToken))
|
||||
assert.Equal(t, true, GrantPassword.Is(GrantPassword))
|
||||
assert.Equal(t, false, GrantClientCredentials.Is(GrantPassword))
|
||||
assert.Equal(t, false, GrantClientCredentials.Is(GrantRefreshToken))
|
||||
@@ -36,6 +38,7 @@ func TestGrantType_IsNot(t *testing.T) {
|
||||
assert.Equal(t, false, GrantUndefined.IsNot(GrantUndefined))
|
||||
assert.Equal(t, false, GrantClientCredentials.IsNot(GrantClientCredentials))
|
||||
assert.Equal(t, false, GrantPassword.IsNot(GrantPassword))
|
||||
assert.Equal(t, false, GrantToken.IsNot(GrantToken))
|
||||
assert.Equal(t, true, GrantClientCredentials.IsNot(GrantPassword))
|
||||
assert.Equal(t, true, GrantClientCredentials.IsNot(GrantRefreshToken))
|
||||
assert.Equal(t, true, GrantClientCredentials.IsNot(GrantAuthorizationCode))
|
||||
@@ -57,6 +60,7 @@ func TestGrantType_Pretty(t *testing.T) {
|
||||
assert.Equal(t, "CLI", GrantCLI.Pretty())
|
||||
assert.Equal(t, "Client Credentials", GrantClientCredentials.Pretty())
|
||||
assert.Equal(t, "Session", GrantSession.Pretty())
|
||||
assert.Equal(t, "Token", GrantToken.Pretty())
|
||||
assert.Equal(t, "Password", GrantPassword.Pretty())
|
||||
assert.Equal(t, "Refresh Token", GrantRefreshToken.Pretty())
|
||||
assert.Equal(t, "Authorization Code", GrantAuthorizationCode.Pretty())
|
||||
@@ -98,6 +102,7 @@ func TestGrant(t *testing.T) {
|
||||
assert.Equal(t, GrantCLI, Grant("cli"))
|
||||
assert.Equal(t, GrantImplicit, Grant("implicit"))
|
||||
assert.Equal(t, GrantSession, Grant("session"))
|
||||
assert.Equal(t, GrantToken, Grant("token"))
|
||||
assert.Equal(t, GrantPassword, Grant("pass"))
|
||||
assert.Equal(t, GrantPassword, Grant("password"))
|
||||
assert.Equal(t, GrantClientCredentials, Grant("client credentials"))
|
||||
|
||||
Reference in New Issue
Block a user