1
0
forked from baron/baron-sso

offline_access 기본 스코프 추가 및 refresh_token 발급 확인

This commit is contained in:
2026-06-12 14:54:49 +09:00
parent fb7a05797c
commit ca15e2a35c
8 changed files with 41 additions and 21 deletions

View File

@@ -8430,7 +8430,7 @@ func buildHydraAuthorizationURL(clientID string, scopes []string, redirectURIs [
seen := map[string]struct{}{} seen := map[string]struct{}{}
for _, scope := range append([]string{"openid"}, scopes...) { for _, scope := range append([]string{"openid"}, scopes...) {
scope = strings.TrimSpace(scope) scope = strings.TrimSpace(scope)
if scope == "" || isRefreshTokenScopeAlias(scope) { if scope == "" || isLegacyRefreshTokenScopeAlias(scope) {
continue continue
} }
if _, ok := seen[scope]; ok { if _, ok := seen[scope]; ok {

View File

@@ -464,7 +464,7 @@ func normalizeScopesInConsentOrder(scopes []string) []string {
appendIfPresent := func(scope string) { appendIfPresent := func(scope string) {
scope = strings.TrimSpace(scope) scope = strings.TrimSpace(scope)
if scope == "" || isRefreshTokenScopeAlias(scope) { if scope == "" || isLegacyRefreshTokenScopeAlias(scope) {
return return
} }
if _, ok := seen[scope]; ok { if _, ok := seen[scope]; ok {
@@ -485,7 +485,7 @@ func normalizeScopesInConsentOrder(scopes []string) []string {
for _, scope := range combined { for _, scope := range combined {
scope = strings.TrimSpace(scope) scope = strings.TrimSpace(scope)
if scope == "" || isRefreshTokenScopeAlias(scope) { if scope == "" || isLegacyRefreshTokenScopeAlias(scope) {
continue continue
} }
if _, ok := seen[scope]; ok { if _, ok := seen[scope]; ok {

View File

@@ -9,6 +9,7 @@ import (
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
"strings"
"testing" "testing"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
@@ -153,7 +154,7 @@ func TestMergeRequestedScopesWithClientRequirements_StripsRefreshTokenScopeAlias
[]string{"openid", "offline", "profile", "offline_access"}, []string{"openid", "offline", "profile", "offline_access"},
) )
assert.Equal(t, []string{"openid", "tenant", "profile", "email"}, merged) assert.Equal(t, []string{"openid", "tenant", "profile", "offline_access", "email"}, merged)
} }
func TestBuildHydraAuthorizationURL_StripsRefreshTokenScopeAliases(t *testing.T) { func TestBuildHydraAuthorizationURL_StripsRefreshTokenScopeAliases(t *testing.T) {
@@ -166,10 +167,11 @@ func TestBuildHydraAuthorizationURL_StripsRefreshTokenScopeAliases(t *testing.T)
parsed, err := url.Parse(urlString) parsed, err := url.Parse(urlString)
assert.NoError(t, err) assert.NoError(t, err)
scopes := parsed.Query().Get("scope") scopes := parsed.Query().Get("scope")
scopeItems := strings.Fields(scopes)
assert.Equal(t, "openid profile email", scopes) assert.Equal(t, "openid profile offline_access email", scopes)
assert.NotContains(t, scopes, "offline") assert.NotContains(t, scopeItems, "offline")
assert.NotContains(t, scopes, "offline_access") assert.Contains(t, scopeItems, "offline_access")
} }
func TestGetConsentRequest_DeniesTenantAccess(t *testing.T) { func TestGetConsentRequest_DeniesTenantAccess(t *testing.T) {

View File

@@ -3828,7 +3828,7 @@ func requestIncludesInlineHeadlessJWKS(req clientUpsertRequest) bool {
} }
func defaultClientScopes() []string { func defaultClientScopes() []string {
return []string{"openid", "profile", "email"} return []string{"openid", "profile", "email", "offline_access"}
} }
func defaultGrantTypes() []string { func defaultGrantTypes() []string {
@@ -3848,7 +3848,7 @@ func normalizeClientScopes(scopes []string) []string {
seen := make(map[string]struct{}, len(scopes)) seen := make(map[string]struct{}, len(scopes))
for _, scope := range scopes { for _, scope := range scopes {
scope = strings.TrimSpace(scope) scope = strings.TrimSpace(scope)
if scope == "" || isRefreshTokenScopeAlias(scope) { if scope == "" || isLegacyRefreshTokenScopeAlias(scope) {
continue continue
} }
if _, ok := seen[scope]; ok { if _, ok := seen[scope]; ok {
@@ -3860,9 +3860,9 @@ func normalizeClientScopes(scopes []string) []string {
return normalized return normalized
} }
func isRefreshTokenScopeAlias(scope string) bool { func isLegacyRefreshTokenScopeAlias(scope string) bool {
switch strings.ToLower(strings.TrimSpace(scope)) { switch strings.ToLower(strings.TrimSpace(scope)) {
case "offline", "offline_access": case "offline":
return true return true
default: default:
return false return false

View File

@@ -2229,9 +2229,9 @@ func TestCreateClient_StripsOfflineScopesAndKeepsRefreshTokenGrant(t *testing.T)
resp, _ := app.Test(req, -1) resp, _ := app.Test(req, -1)
assert.Equal(t, http.StatusCreated, resp.StatusCode) assert.Equal(t, http.StatusCreated, resp.StatusCode)
assert.Equal(t, "openid profile email", captured.Scope) assert.Equal(t, "openid profile offline_access email", captured.Scope)
assert.NotContains(t, strings.Fields(captured.Scope), "offline") assert.NotContains(t, strings.Fields(captured.Scope), "offline")
assert.NotContains(t, strings.Fields(captured.Scope), "offline_access") assert.Contains(t, strings.Fields(captured.Scope), "offline_access")
assert.Contains(t, captured.GrantTypes, "refresh_token") assert.Contains(t, captured.GrantTypes, "refresh_token")
} }
@@ -2296,9 +2296,9 @@ func TestUpdateClient_StripsStoredOfflineScopesAndKeepsRefreshTokenGrant(t *test
resp, _ := app.Test(req, -1) resp, _ := app.Test(req, -1)
assert.Equal(t, http.StatusOK, resp.StatusCode) assert.Equal(t, http.StatusOK, resp.StatusCode)
assert.Equal(t, "openid profile email", captured.Scope) assert.Equal(t, "openid profile offline_access email", captured.Scope)
assert.NotContains(t, strings.Fields(captured.Scope), "offline") assert.NotContains(t, strings.Fields(captured.Scope), "offline")
assert.NotContains(t, strings.Fields(captured.Scope), "offline_access") assert.Contains(t, strings.Fields(captured.Scope), "offline_access")
assert.Contains(t, captured.GrantTypes, "refresh_token") assert.Contains(t, captured.GrantTypes, "refresh_token")
} }

View File

@@ -409,7 +409,7 @@ describe("ClientGeneralPage RP claims", () => {
); );
}); });
it("shows supported scopes and custom claims without integrated offline_access from the add scope button", async () => { it("shows supported scopes including offline_access and custom claims from the add scope button", async () => {
const { container } = await renderPage(); const { container } = await renderPage();
const addScopeButton = Array.from( const addScopeButton = Array.from(
@@ -422,7 +422,7 @@ describe("ClientGeneralPage RP claims", () => {
}); });
await flush(); await flush();
expect(container.textContent).not.toContain("offline_access"); expect(container.textContent).toContain("offline_access");
expect(container.textContent).toContain("old_claim"); expect(container.textContent).toContain("old_claim");
const customClaimButton = Array.from( const customClaimButton = Array.from(

View File

@@ -659,6 +659,15 @@ function ClientGeneralPage() {
description: t("msg.dev.clients.scopes.email", "이메일 주소 접근"), description: t("msg.dev.clients.scopes.email", "이메일 주소 접근"),
mandatory: false, mandatory: false,
}, },
{
id: "5",
name: "offline_access",
description: t(
"msg.dev.clients.scopes.offline_access",
"refresh token 발급 요청",
),
mandatory: false,
},
]); ]);
const [idTokenClaims, setIdTokenClaims] = useState<IdTokenClaimItem[]>([]); const [idTokenClaims, setIdTokenClaims] = useState<IdTokenClaimItem[]>([]);
const browserTimeZone = useMemo(() => getBrowserTimeZone(), []); const browserTimeZone = useMemo(() => getBrowserTimeZone(), []);
@@ -759,6 +768,15 @@ function ClientGeneralPage() {
description: tenantScopeDescription, description: tenantScopeDescription,
source: "standard", source: "standard",
}, },
{
id: "standard-offline-access",
name: "offline_access",
description: t(
"msg.dev.clients.scopes.offline_access",
"refresh token 발급 요청",
),
source: "standard",
},
], ],
[tenantScopeDescription], [tenantScopeDescription],
); );

View File

@@ -99,7 +99,7 @@ test.describe("DevFront RP claim cache", () => {
await expect(claimKeyInput).toHaveValue("new_claim"); await expect(claimKeyInput).toHaveValue("new_claim");
}); });
test("adds supported scopes and custom claim keys from the scope picker without offline_access", async ({ test("adds supported scopes and custom claim keys from the scope picker including offline_access", async ({
page, page,
}) => { }) => {
const state = { const state = {
@@ -142,9 +142,9 @@ test.describe("DevFront RP claim cache", () => {
.getByRole("button", { name: /스코프 추가|Scope 추가|Add Scope/i }) .getByRole("button", { name: /스코프 추가|Scope 추가|Add Scope/i })
.click(); .click();
await expect(page.getByText("offline_access", { exact: true })).toHaveCount( await expect(
0, page.getByText("offline_access", { exact: true }),
); ).toBeVisible();
await expect( await expect(
page.getByRole("button", { name: /employee_code/ }), page.getByRole("button", { name: /employee_code/ }),
).toBeVisible(); ).toBeVisible();