Skip to content

Commit

Permalink
feat: adjust identity api to accept page token
Browse files Browse the repository at this point in the history
closes #256
  • Loading branch information
shipperizer committed Apr 9, 2024
1 parent 7c2d3f6 commit beb0d42
Show file tree
Hide file tree
Showing 9 changed files with 53 additions and 72 deletions.
2 changes: 1 addition & 1 deletion cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ func serve() {

schemasConfig := &schemas.Config{
K8s: k8sCoreV1,
Kratos: extCfg.KratosPublic().IdentityApi(),
Kratos: extCfg.KratosPublic().IdentityAPI(),
Name: specs.SchemasConfigMapName,
Namespace: specs.SchemasConfigMapNamespace,
}
Expand Down
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,12 @@ require (
github.com/openfga/go-sdk v0.3.4
github.com/openfga/language/pkg/go v0.0.0-20240122114256-aaa86ab89379
github.com/ory/hydra-client-go/v2 v2.1.1
github.com/ory/kratos-client-go v1.0.0
github.com/ory/kratos-client-go v1.1.0
github.com/ory/oathkeeper-client-go v0.40.6
github.com/prometheus/client_golang v1.17.0
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.8.4
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.45.0
go.opentelemetry.io/contrib/propagators/jaeger v1.20.0
go.opentelemetry.io/otel v1.19.0
Expand Down
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ github.com/openfga/language/pkg/go v0.0.0-20240122114256-aaa86ab89379 h1:j42rKsj
github.com/openfga/language/pkg/go v0.0.0-20240122114256-aaa86ab89379/go.mod h1:dHJaJ7H5tViBCPidTsfl3IOd152FhYxWFQmZXOhZ2pw=
github.com/ory/hydra-client-go/v2 v2.1.1 h1:3JatU9uFbw5XhF3lgPCas1l1Kok2v5Mq1p26zZwGHNg=
github.com/ory/hydra-client-go/v2 v2.1.1/go.mod h1:IiIwChp/9wRvPoyFQblqPvg78uVishCCrV9+/M7Pl34=
github.com/ory/kratos-client-go v1.0.0 h1:mm32FMJrt4pBv2KEuhuNtiewJApc8c1Kmz0+WFHhOMA=
github.com/ory/kratos-client-go v1.0.0/go.mod h1:a2Tl4cgQAxsjR59w3EfnH5hengabjXUHiEVDzdqiZI0=
github.com/ory/kratos-client-go v1.1.0 h1:mCk5wxNTxjYq/sbZfoEY/JcxuBtuixStHD14Y0sU1E8=
github.com/ory/kratos-client-go v1.1.0/go.mod h1:ultwfjWsBxshnZgopqQ3DrKOe/t6SXsM+KKOd21PaTQ=
github.com/ory/oathkeeper-client-go v0.40.6 h1:Qm/odusPn6DTJ8mXXElNzfXYpcD5wqmb/k3dOYKUfTg=
github.com/ory/oathkeeper-client-go v0.40.6/go.mod h1:9JUPR04XPH3e53TYbfu+KveCnTIYtlSTJiQ23rEQLJI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down Expand Up @@ -147,6 +147,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
Expand Down
4 changes: 2 additions & 2 deletions internal/kratos/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ type Client struct {
c *client.APIClient
}

func (c *Client) IdentityApi() client.IdentityApi {
return c.c.IdentityApi
func (c *Client) IdentityAPI() client.IdentityAPI {
return c.c.IdentityAPI
}

func NewClient(url string, debug bool) *Client {
Expand Down
7 changes: 5 additions & 2 deletions pkg/identities/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ type UpdateIdentityRequest struct {

type API struct {
service ServiceInterface
baseURL string
validator *validator.Validate

logger logging.LoggerInterface
Expand Down Expand Up @@ -64,7 +63,7 @@ func (a *API) handleList(w http.ResponseWriter, r *http.Request) {

credID := r.URL.Query().Get("credID")

ids, err := a.service.ListIdentities(r.Context(), pagination.Page, pagination.Size, credID)
ids, err := a.service.ListIdentities(r.Context(), pagination.Size, pagination.PageToken, credID)

if err != nil {
rr := a.error(ids.Error)
Expand All @@ -75,6 +74,10 @@ func (a *API) handleList(w http.ResponseWriter, r *http.Request) {
return
}

// TODO @shipperizer improve on this, see if better to stick with link headers
pagination.Next = ids.Tokens.Next
pagination.Prev = ids.Tokens.Prev

w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(
types.Response{
Expand Down
48 changes: 28 additions & 20 deletions pkg/identities/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/http/httptest"
"reflect"
Expand All @@ -19,14 +18,16 @@ import (

"github.com/canonical/identity-platform-admin-ui/internal/http/types"

"io"

kClient "github.com/ory/kratos-client-go"
)

//go:generate mockgen -build_flags=--mod=mod -package identities -destination ./mock_logger.go -source=../../internal/logging/interfaces.go
//go:generate mockgen -build_flags=--mod=mod -package identities -destination ./mock_interfaces.go -source=./interfaces.go
//go:generate mockgen -build_flags=--mod=mod -package identities -destination ./mock_monitor.go -source=../../internal/monitoring/interfaces.go
//go:generate mockgen -build_flags=--mod=mod -package identities -destination ./mock_tracing.go go.opentelemetry.io/otel/trace Tracer
//go:generate mockgen -build_flags=--mod=mod -package identities -destination ./mock_kratos.go github.com/ory/kratos-client-go IdentityApi
//go:generate mockgen -build_flags=--mod=mod -package identities -destination ./mock_kratos.go github.com/ory/kratos-client-go IdentityAPI

func TestHandleListSuccess(t *testing.T) {
ctrl := gomock.NewController(t)
Expand All @@ -43,11 +44,19 @@ func TestHandleListSuccess(t *testing.T) {

req := httptest.NewRequest(http.MethodGet, "/api/v0/identities", nil)
values := req.URL.Query()
values.Add("page", "1")
values.Add("size", "100")
req.URL.RawQuery = values.Encode()

mockService.EXPECT().ListIdentities(gomock.Any(), int64(1), int64(100), "").Return(&IdentityData{Identities: identities}, nil)
mockService.EXPECT().ListIdentities(gomock.Any(), int64(100), "", "").Return(
&IdentityData{
Identities: identities,
Tokens: types.NavigationTokens{
Next: "eyJvZmZzZXQiOiIyNTAiLCJ2IjoyfQ",
Prev: "eyJvZmZzZXQiOiItMjUwIiwidiI6Mn0",
},
},
nil,
)

w := httptest.NewRecorder()
mux := chi.NewMux()
Expand All @@ -57,7 +66,7 @@ func TestHandleListSuccess(t *testing.T) {

res := w.Result()
defer res.Body.Close()
data, err := ioutil.ReadAll(res.Body)
data, err := io.ReadAll(io.Reader(res.Body))

if err != nil {
t.Errorf("expected error to be nil got %v", err)
Expand Down Expand Up @@ -113,7 +122,6 @@ func TestHandleListFailAndPropagatesKratosError(t *testing.T) {

req := httptest.NewRequest(http.MethodGet, "/api/v0/identities", nil)
values := req.URL.Query()
values.Add("page", "1")
values.Add("size", "100")
req.URL.RawQuery = values.Encode()

Expand All @@ -122,7 +130,7 @@ func TestHandleListFailAndPropagatesKratosError(t *testing.T) {
gerr.SetMessage("teapot error")
gerr.SetReason("teapot is broken")

mockService.EXPECT().ListIdentities(gomock.Any(), int64(1), int64(100), "").Return(&IdentityData{Identities: make([]kClient.Identity, 0), Error: gerr}, fmt.Errorf("error"))
mockService.EXPECT().ListIdentities(gomock.Any(), int64(100), "", "").Return(&IdentityData{Identities: make([]kClient.Identity, 0), Error: gerr}, fmt.Errorf("error"))

w := httptest.NewRecorder()
mux := chi.NewMux()
Expand All @@ -132,7 +140,7 @@ func TestHandleListFailAndPropagatesKratosError(t *testing.T) {

res := w.Result()
defer res.Body.Close()
data, err := ioutil.ReadAll(res.Body)
data, err := io.ReadAll(io.Reader(res.Body))

if err != nil {
t.Errorf("expected error to be nil got %v", err)
Expand Down Expand Up @@ -178,7 +186,7 @@ func TestHandleDetailSuccess(t *testing.T) {

res := w.Result()
defer res.Body.Close()
data, err := ioutil.ReadAll(res.Body)
data, err := io.ReadAll(io.Reader(res.Body))

if err != nil {
t.Errorf("expected error to be nil got %v", err)
Expand Down Expand Up @@ -254,7 +262,7 @@ func TestHandleDetailFailAndPropagatesKratosError(t *testing.T) {

res := w.Result()
defer res.Body.Close()
data, err := ioutil.ReadAll(res.Body)
data, err := io.ReadAll(io.Reader(res.Body))

if err != nil {
t.Errorf("expected error to be nil got %v", err)
Expand Down Expand Up @@ -304,7 +312,7 @@ func TestHandleCreateSuccess(t *testing.T) {

res := w.Result()
defer res.Body.Close()
data, err := ioutil.ReadAll(res.Body)
data, err := io.ReadAll(io.Reader(res.Body))

if err != nil {
t.Errorf("expected error to be nil got %v", err)
Expand Down Expand Up @@ -385,7 +393,7 @@ func TestHandleCreateFailAndPropagatesKratosError(t *testing.T) {

res := w.Result()
defer res.Body.Close()
data, err := ioutil.ReadAll(res.Body)
data, err := io.ReadAll(io.Reader(res.Body))

if err != nil {
t.Errorf("expected error to be nil got %v", err)
Expand Down Expand Up @@ -426,7 +434,7 @@ func TestHandleCreateFailBadRequest(t *testing.T) {

res := w.Result()
defer res.Body.Close()
data, err := ioutil.ReadAll(res.Body)
data, err := io.ReadAll(io.Reader(res.Body))

if err != nil {
t.Errorf("expected error to be nil got %v", err)
Expand Down Expand Up @@ -457,7 +465,7 @@ func TestHandleUpdateSuccess(t *testing.T) {
identity := kClient.NewIdentity(credID, "test.json", "https://test.com/test.json", map[string]string{"name": "name"})
identityBody := kClient.NewUpdateIdentityBodyWithDefaults()
identityBody.SchemaId = identity.SchemaId
identityBody.SetState(kClient.IDENTITYSTATE_ACTIVE)
identityBody.SetState("active")
identityBody.Traits = map[string]interface{}{"name": "name"}
identityBody.AdditionalProperties = map[string]interface{}{"name": "name"}

Expand All @@ -475,7 +483,7 @@ func TestHandleUpdateSuccess(t *testing.T) {

res := w.Result()
defer res.Body.Close()
data, err := ioutil.ReadAll(res.Body)
data, err := io.ReadAll(io.Reader(res.Body))

if err != nil {
t.Errorf("expected error to be nil got %v", err)
Expand Down Expand Up @@ -536,7 +544,7 @@ func TestHandleUpdateFailAndPropagatesKratosError(t *testing.T) {
identityBody := kClient.NewUpdateIdentityBodyWithDefaults()
identityBody.SchemaId = "test.json"
identityBody.Traits = map[string]interface{}{"name": "name"}
identityBody.SetState(kClient.IDENTITYSTATE_ACTIVE)
identityBody.SetState("active")
identityBody.AdditionalProperties = map[string]interface{}{"name": "name"}

payload, err := json.Marshal(identityBody)
Expand All @@ -557,7 +565,7 @@ func TestHandleUpdateFailAndPropagatesKratosError(t *testing.T) {

res := w.Result()
defer res.Body.Close()
data, err := ioutil.ReadAll(res.Body)
data, err := io.ReadAll(io.Reader(res.Body))

if err != nil {
t.Errorf("expected error to be nil got %v", err)
Expand Down Expand Up @@ -598,7 +606,7 @@ func TestHandleUpdateFailBadRequest(t *testing.T) {

res := w.Result()
defer res.Body.Close()
data, err := ioutil.ReadAll(res.Body)
data, err := io.ReadAll(io.Reader(res.Body))

if err != nil {
t.Errorf("expected error to be nil got %v", err)
Expand Down Expand Up @@ -639,7 +647,7 @@ func TestHandleRemoveSuccess(t *testing.T) {

res := w.Result()
defer res.Body.Close()
data, err := ioutil.ReadAll(res.Body)
data, err := io.ReadAll(io.Reader(res.Body))

if err != nil {
t.Errorf("expected error to be nil got %v", err)
Expand Down Expand Up @@ -687,7 +695,7 @@ func TestHandleRemoveFailAndPropagatesKratosError(t *testing.T) {

res := w.Result()
defer res.Body.Close()
data, err := ioutil.ReadAll(res.Body)
data, err := io.ReadAll(io.Reader(res.Body))

if err != nil {
t.Errorf("expected error to be nil got %v", err)
Expand Down
49 changes: 8 additions & 41 deletions pkg/identities/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,11 @@ import (
"fmt"
"io"
"net/http"
"net/url"

kClient "github.com/ory/kratos-client-go"
"github.com/tomnomnom/linkheader"
"go.opentelemetry.io/otel/trace"

"github.com/canonical/identity-platform-admin-ui/internal/http/types"
"github.com/canonical/identity-platform-admin-ui/internal/logging"
"github.com/canonical/identity-platform-admin-ui/internal/monitoring"
)
Expand All @@ -27,16 +26,9 @@ type Service struct {
logger logging.LoggerInterface
}

// TODO @shipperizer worth offloading to a different place as it's going to be reused
type PaginationTokens struct {
First string
Prev string
Next string
}

type IdentityData struct {
Identities []kClient.Identity
Tokens PaginationTokens
Tokens types.NavigationTokens
Error *kClient.GenericError
}

Expand All @@ -55,36 +47,6 @@ func (s *Service) buildListRequest(ctx context.Context, size int64, token, credI
return r
}

func (s *Service) parseLinkURL(linkURL string) string {
u, err := url.Parse(linkURL)

if err != nil {
s.logger.Errorf("failed to parse link header successfully: %s", err)
return ""
}

return u.Query().Get("page_token")
}

func (s *Service) parsePagination(r *http.Response) PaginationTokens {
links := linkheader.Parse(r.Header.Get("Link"))

pagination := PaginationTokens{}

for _, link := range links {
switch link.Rel {
case "first":
pagination.First = s.parseLinkURL(link.URL)
case "next":
pagination.Next = s.parseLinkURL(link.URL)
case "prev":
pagination.Prev = s.parseLinkURL(link.URL)
}
}

return pagination
}

func (s *Service) parseError(r *http.Response) *kClient.GenericError {
gerr := KratosError{Error: kClient.NewGenericErrorWithDefaults()}

Expand Down Expand Up @@ -114,7 +76,12 @@ func (s *Service) ListIdentities(ctx context.Context, size int64, token, credID
data.Error = s.parseError(rr)
}

data.Tokens = s.parsePagination(rr)
if navTokens, err := types.ParseLinkTokens(rr.Header); err != nil {
s.logger.Warnf("failed parsing link header: %s", err)
} else {
data.Tokens = navTokens
}

data.Identities = identities

// TODO @shipperizer check if identities is defaulting to empty slice inside kratos-client
Expand Down
4 changes: 2 additions & 2 deletions pkg/identities/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ func TestListIdentitiesSuccess(t *testing.T) {
}

if !reflect.DeepEqual(
[]string{ids.Tokens.First, ids.Tokens.Next, ids.Tokens.Prev},
[]string{"eyJvZmZzZXQiOiIwIiwidiI6Mn0", "eyJvZmZzZXQiOiIyNTAiLCJ2IjoyfQ", "eyJvZmZzZXQiOiItMjUwIiwidiI6Mn0"},
[]string{ids.Tokens.Next, ids.Tokens.Prev},
[]string{"eyJvZmZzZXQiOiIyNTAiLCJ2IjoyfQ", "eyJvZmZzZXQiOiItMjUwIiwidiI6Mn0"},
) {
t.Fatalf("expected tokens to be set, not %v", ids.Tokens)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/web/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func NewRouter(idpConfig *idp.Config, schemasConfig *schemas.Config, rulesConfig
metrics.NewAPI(logger).RegisterEndpoints(router)

identitiesAPI := identities.NewAPI(
identities.NewService(externalConfig.KratosAdmin().IdentityApi(), tracer, monitor, logger),
identities.NewService(externalConfig.KratosAdmin().IdentityAPI(), tracer, monitor, logger),
logger,
)
identitiesAPI.RegisterEndpoints(router)
Expand Down

0 comments on commit beb0d42

Please sign in to comment.