snapshot of https://github.com/cortezaproject/corteza.git (2024.9.x, 2026-09-18; blobs > 131072B omitted)
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
Some checks failed
SonarQube / SonarQube (push) Failing after 5m28s
E2e test on main branch push / server and client test (map[name:admin port:8080]) (push) Failing after 3m33s
Server and lib tests / Library test (js) (push) Failing after 1m36s
Server and lib tests / Library test (vue) (push) Successful in 2m24s
Server and lib tests / Web client test (admin) (push) Successful in 2m52s
Server and lib tests / Web client test (compose) (push) Successful in 3m23s
Server and lib tests / Web client test (discovery) (push) Successful in 2m49s
Server and lib tests / Web client test (one) (push) Successful in 2m52s
Server and lib tests / Web client test (privacy) (push) Successful in 2m44s
Server and lib tests / Web client test (reporter) (push) Successful in 2m56s
Server and lib tests / Web client test (workflow) (push) Successful in 3m0s
Server and lib tests / Server tests (push) Failing after 4m55s
E2e test on main branch push / notify-failure (push) Failing after 9s
Server and lib tests / Send matrix testing notification (push) Successful in 2s
Mark stale issues and pull requests / stale (push) Successful in 40s
This commit is contained in:
389
server/auth/handlers/handle_oauth2_test.go
Normal file
389
server/auth/handlers/handle_oauth2_test.go
Normal file
@@ -0,0 +1,389 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cortezaproject/corteza/server/auth/request"
|
||||
"github.com/cortezaproject/corteza/server/auth/settings"
|
||||
"github.com/cortezaproject/corteza/server/pkg/auth"
|
||||
"github.com/cortezaproject/corteza/server/system/types"
|
||||
"github.com/go-chi/jwtauth"
|
||||
oauth2models "github.com/go-oauth2/oauth2/v4/models"
|
||||
"github.com/lestrrat-go/jwx/jwt"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func Test_oauth2AuthorizeSuccess(t *testing.T) {
|
||||
var (
|
||||
user = makeMockUser()
|
||||
|
||||
req = &http.Request{
|
||||
Form: url.Values{},
|
||||
PostForm: url.Values{},
|
||||
}
|
||||
|
||||
oauthService oauth2Service
|
||||
authService authService
|
||||
authHandlers *AuthHandlers
|
||||
authReq *request.AuthReq
|
||||
)
|
||||
|
||||
tcc := []testingExpect{
|
||||
{
|
||||
name: "authorization success",
|
||||
payload: -1,
|
||||
err: "",
|
||||
template: "",
|
||||
fn: func(_ *settings.Settings) {
|
||||
oauthService = &oauth2ServiceMocked{
|
||||
handleAuthorizeRequest: func(w http.ResponseWriter, r *http.Request) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "authorization failure",
|
||||
payload: http.StatusInternalServerError,
|
||||
err: "not authorized",
|
||||
template: TmplInternalError,
|
||||
fn: func(_ *settings.Settings) {
|
||||
oauthService = &oauth2ServiceMocked{
|
||||
handleAuthorizeRequest: func(w http.ResponseWriter, r *http.Request) error {
|
||||
return fmt.Errorf("not authorized")
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcc {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
rq := require.New(t)
|
||||
|
||||
authSettings := &settings.Settings{}
|
||||
|
||||
tc.fn(authSettings)
|
||||
|
||||
authHandlers = &AuthHandlers{
|
||||
Log: zap.NewNop(),
|
||||
AuthService: authService,
|
||||
Settings: authSettings,
|
||||
OAuth2: oauthService,
|
||||
}
|
||||
authReq = prepareClientAuthReq(authHandlers, req, user)
|
||||
|
||||
err := authHandlers.oauth2Authorize(authReq)
|
||||
|
||||
rq.NoError(err)
|
||||
rq.Equal(tc.template, authReq.Template)
|
||||
rq.Equal(tc.payload, authReq.Status)
|
||||
|
||||
if tc.err != "" {
|
||||
rq.EqualError(errors.New(tc.err), authReq.Data["error"].(error).Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_oauth2AuthorizeSuccessSetParams(t *testing.T) {
|
||||
var (
|
||||
user = makeMockUser()
|
||||
|
||||
req = &http.Request{
|
||||
Form: url.Values{},
|
||||
PostForm: url.Values{},
|
||||
}
|
||||
|
||||
authService authService
|
||||
authHandlers *AuthHandlers
|
||||
authReq *request.AuthReq
|
||||
|
||||
authSettings = &settings.Settings{}
|
||||
|
||||
rq = require.New(t)
|
||||
)
|
||||
|
||||
oauthService := &oauth2ServiceMocked{
|
||||
handleAuthorizeRequest: func(w http.ResponseWriter, r *http.Request) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
authHandlers = &AuthHandlers{
|
||||
Log: zap.NewNop(),
|
||||
AuthService: authService,
|
||||
Settings: authSettings,
|
||||
OAuth2: oauthService,
|
||||
}
|
||||
authReq = prepareClientAuthReq(authHandlers, req, user)
|
||||
authReq.Session.Values["oauth2AuthParams"] = url.Values{"foo": []string{"bar"}}
|
||||
|
||||
err := authHandlers.oauth2Authorize(authReq)
|
||||
|
||||
rq.NoError(err)
|
||||
rq.Equal("", authReq.Template)
|
||||
rq.Equal(-1, authReq.Status)
|
||||
rq.Equal(nil, authReq.Data["error"])
|
||||
}
|
||||
|
||||
func Test_generateIdToken(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
)
|
||||
|
||||
signed, err := generateIdToken(
|
||||
&types.User{},
|
||||
&types.AuthClient{
|
||||
Secret: "correct horse battery stable",
|
||||
},
|
||||
&oauth2models.Token{},
|
||||
"http://cortezaproject.org",
|
||||
)
|
||||
|
||||
req.NoError(err)
|
||||
req.NotEmpty(signed)
|
||||
}
|
||||
|
||||
func Test_generateIdTokenClaims(t *testing.T) {
|
||||
var (
|
||||
updatedAt = time.Now()
|
||||
|
||||
user = &types.User{
|
||||
ID: 42,
|
||||
Name: "John Doe",
|
||||
Handle: "jdoe",
|
||||
Email: "john@example.tld",
|
||||
EmailConfirmed: true,
|
||||
UpdatedAt: &updatedAt,
|
||||
Meta: &types.UserMeta{PreferredLanguage: "sl"},
|
||||
}
|
||||
|
||||
client = &types.AuthClient{ID: 1, Secret: "correct horse battery stable"}
|
||||
)
|
||||
|
||||
tcc := []struct {
|
||||
name string
|
||||
scope string
|
||||
present []string
|
||||
absent []string
|
||||
}{
|
||||
{
|
||||
name: "without profile scope",
|
||||
scope: "openid",
|
||||
present: []string{"sub", "user_id", "email", "email_verified", "iat", "aud"},
|
||||
absent: []string{"name", "preferred_username", "locale", "updated_at"},
|
||||
},
|
||||
{
|
||||
name: "with profile scope",
|
||||
scope: "openid profile",
|
||||
present: []string{"sub", "email", "name", "preferred_username", "locale", "updated_at"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcc {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var (
|
||||
req = require.New(t)
|
||||
ti = &oauth2models.Token{}
|
||||
)
|
||||
|
||||
ti.SetScope(tc.scope)
|
||||
|
||||
signed, err := generateIdToken(user, client, ti, "https://example.tld/auth")
|
||||
req.NoError(err)
|
||||
|
||||
token, err := jwt.Parse(signed)
|
||||
req.NoError(err)
|
||||
|
||||
for _, claim := range tc.present {
|
||||
_, has := token.Get(claim)
|
||||
req.True(has, "expecting claim %q", claim)
|
||||
}
|
||||
|
||||
for _, claim := range tc.absent {
|
||||
_, has := token.Get(claim)
|
||||
req.False(has, "not expecting claim %q", claim)
|
||||
}
|
||||
|
||||
req.Equal("https://example.tld/auth", token.Issuer())
|
||||
req.Equal("42", token.Subject())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_oauth2Userinfo(t *testing.T) {
|
||||
var (
|
||||
updatedAt = time.Now()
|
||||
|
||||
user = &types.User{
|
||||
ID: 42,
|
||||
Name: "John Doe",
|
||||
Handle: "jdoe",
|
||||
Email: "john@example.tld",
|
||||
EmailConfirmed: true,
|
||||
UpdatedAt: &updatedAt,
|
||||
Meta: &types.UserMeta{PreferredLanguage: "sl"},
|
||||
}
|
||||
)
|
||||
|
||||
// swap the global token issuer for one we can sign test tokens with
|
||||
original := auth.TokenIssuer
|
||||
t.Cleanup(func() { auth.TokenIssuer = original })
|
||||
|
||||
issuer, err := auth.NewTokenIssuer(
|
||||
auth.WithSecretSigner("correct horse battery stable"),
|
||||
auth.WithLookup(func(ctx context.Context, tokenID string) error { return nil }),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
auth.TokenIssuer = issuer
|
||||
|
||||
tcc := []struct {
|
||||
name string
|
||||
scope string
|
||||
noToken bool
|
||||
notFound bool
|
||||
status int
|
||||
claims map[string]interface{}
|
||||
absent []string
|
||||
}{
|
||||
{
|
||||
name: "openid only",
|
||||
scope: "openid",
|
||||
status: http.StatusOK,
|
||||
claims: map[string]interface{}{"sub": "42"},
|
||||
absent: []string{"name", "preferred_username", "locale", "email", "email_verified"},
|
||||
},
|
||||
{
|
||||
name: "profile scope",
|
||||
scope: "openid profile",
|
||||
status: http.StatusOK,
|
||||
claims: map[string]interface{}{
|
||||
"sub": "42",
|
||||
"name": "John Doe",
|
||||
"preferred_username": "jdoe",
|
||||
"locale": "sl",
|
||||
},
|
||||
absent: []string{"email", "email_verified"},
|
||||
},
|
||||
{
|
||||
name: "email scope",
|
||||
scope: "openid email",
|
||||
status: http.StatusOK,
|
||||
claims: map[string]interface{}{
|
||||
"sub": "42",
|
||||
"email": "john@example.tld",
|
||||
"email_verified": true,
|
||||
},
|
||||
absent: []string{"name", "preferred_username", "locale"},
|
||||
},
|
||||
{
|
||||
name: "no token",
|
||||
noToken: true,
|
||||
status: http.StatusUnauthorized,
|
||||
},
|
||||
{
|
||||
name: "unknown user",
|
||||
scope: "openid",
|
||||
notFound: true,
|
||||
status: http.StatusUnauthorized,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tcc {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var (
|
||||
rq = require.New(t)
|
||||
r = httptest.NewRequest(http.MethodGet, "/oauth2/userinfo", nil)
|
||||
rec = httptest.NewRecorder()
|
||||
)
|
||||
|
||||
if !tc.noToken {
|
||||
signed, err := auth.TokenIssuer.Sign(
|
||||
auth.WithIdentity(auth.Authenticated(user.ID)),
|
||||
auth.WithScope(strings.Split(tc.scope, " ")...),
|
||||
)
|
||||
rq.NoError(err)
|
||||
|
||||
token, err := jwt.Parse(signed)
|
||||
rq.NoError(err)
|
||||
|
||||
r = r.WithContext(jwtauth.NewContext(r.Context(), token, nil))
|
||||
}
|
||||
|
||||
h := &AuthHandlers{
|
||||
Log: zap.NewNop(),
|
||||
UserService: userServiceMocked{
|
||||
findByAny: func(context.Context, interface{}) (*types.User, error) {
|
||||
if tc.notFound {
|
||||
return nil, fmt.Errorf("not found")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
h.oauth2Userinfo(rec, r)
|
||||
rq.Equal(tc.status, rec.Code)
|
||||
|
||||
if tc.status != http.StatusOK {
|
||||
rq.Contains(rec.Header().Get("WWW-Authenticate"), "Bearer")
|
||||
return
|
||||
}
|
||||
|
||||
var response map[string]interface{}
|
||||
rq.NoError(json.Unmarshal(rec.Body.Bytes(), &response))
|
||||
|
||||
for claim, value := range tc.claims {
|
||||
rq.Equal(value, response[claim], "claim %q", claim)
|
||||
}
|
||||
|
||||
for _, claim := range tc.absent {
|
||||
rq.NotContains(response, claim)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SubSplitRoles(t *testing.T) {
|
||||
type (
|
||||
exp struct {
|
||||
id string
|
||||
i string
|
||||
ii []string
|
||||
}
|
||||
)
|
||||
var (
|
||||
req = require.New(t)
|
||||
ti = &oauth2models.Token{}
|
||||
d = make(map[string]interface{})
|
||||
|
||||
tii = []exp{
|
||||
{id: "1", i: "1", ii: []string{}},
|
||||
{id: "1 2", i: "1", ii: []string{"2"}},
|
||||
{id: "1 2 33 444", i: "1", ii: []string{"2", "33", "444"}},
|
||||
}
|
||||
)
|
||||
|
||||
for _, v := range tii {
|
||||
ti.SetUserID(v.id)
|
||||
SubSplit(ti, d)
|
||||
|
||||
req.Equal(v.i, d["sub"])
|
||||
|
||||
if _, is := d["roles"]; is {
|
||||
req.Equal(v.ii, d["roles"])
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user