Skip to content

Commit

Permalink
feat: parse and expose link header from hydra
Browse files Browse the repository at this point in the history
  • Loading branch information
shipperizer committed Apr 9, 2024
1 parent 5a13e4e commit 7c2d3f6
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 99 deletions.
2 changes: 1 addition & 1 deletion pkg/identities/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
)

type ServiceInterface interface {
ListIdentities(context.Context, int64, int64, string) (*IdentityData, error)
ListIdentities(context.Context, int64, string, string) (*IdentityData, error)
GetIdentity(context.Context, string) (*IdentityData, error)
CreateIdentity(context.Context, *kClient.CreateIdentityBody) (*IdentityData, error)
UpdateIdentity(context.Context, string, *kClient.UpdateIdentityBody) (*IdentityData, error)
Expand Down
64 changes: 52 additions & 12 deletions pkg/identities/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,34 @@ 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/logging"
"github.com/canonical/identity-platform-admin-ui/internal/monitoring"
)

type Service struct {
kratos kClient.IdentityApi
kratos kClient.IdentityAPI

tracer trace.Tracer
monitor monitoring.MonitorInterface
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
Error *kClient.GenericError
}

Expand All @@ -35,8 +45,8 @@ type KratosError struct {
Error *kClient.GenericError `json:"error,omitempty"`
}

func (s *Service) buildListRequest(ctx context.Context, page, size int64, credID string) kClient.IdentityApiListIdentitiesRequest {
r := s.kratos.ListIdentities(ctx).Page(page).PerPage(size)
func (s *Service) buildListRequest(ctx context.Context, size int64, token, credID string) kClient.IdentityAPIListIdentitiesRequest {
r := s.kratos.ListIdentities(ctx).PageToken(token).PageSize(size)

if credID != "" {
r = r.CredentialsIdentifier(credID)
Expand All @@ -45,6 +55,36 @@ func (s *Service) buildListRequest(ctx context.Context, page, size int64, credID
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 All @@ -59,13 +99,12 @@ func (s *Service) parseError(r *http.Response) *kClient.GenericError {
return gerr.Error
}

// TODO @shipperizer fix pagination
func (s *Service) ListIdentities(ctx context.Context, page, size int64, credID string) (*IdentityData, error) {
ctx, span := s.tracer.Start(ctx, "kratos.IdentityApi.ListIdentities")
func (s *Service) ListIdentities(ctx context.Context, size int64, token, credID string) (*IdentityData, error) {
ctx, span := s.tracer.Start(ctx, "kratos.IdentityAPI.ListIdentities")
defer span.End()

identities, rr, err := s.kratos.ListIdentitiesExecute(
s.buildListRequest(ctx, page, size, credID),
s.buildListRequest(ctx, size, token, credID),
)

data := new(IdentityData)
Expand All @@ -75,6 +114,7 @@ func (s *Service) ListIdentities(ctx context.Context, page, size int64, credID s
data.Error = s.parseError(rr)
}

data.Tokens = s.parsePagination(rr)
data.Identities = identities

// TODO @shipperizer check if identities is defaulting to empty slice inside kratos-client
Expand All @@ -86,7 +126,7 @@ func (s *Service) ListIdentities(ctx context.Context, page, size int64, credID s
}

func (s *Service) GetIdentity(ctx context.Context, ID string) (*IdentityData, error) {
ctx, span := s.tracer.Start(ctx, "kratos.IdentityApi.GetIdentity")
ctx, span := s.tracer.Start(ctx, "kratos.IdentityAPI.GetIdentity")
defer span.End()

identity, rr, err := s.kratos.GetIdentityExecute(
Expand All @@ -110,7 +150,7 @@ func (s *Service) GetIdentity(ctx context.Context, ID string) (*IdentityData, er
}

func (s *Service) CreateIdentity(ctx context.Context, bodyID *kClient.CreateIdentityBody) (*IdentityData, error) {
ctx, span := s.tracer.Start(ctx, "kratos.IdentityApi.CreateIdentity")
ctx, span := s.tracer.Start(ctx, "kratos.IdentityAPI.CreateIdentity")
defer span.End()

if bodyID == nil {
Expand Down Expand Up @@ -147,7 +187,7 @@ func (s *Service) CreateIdentity(ctx context.Context, bodyID *kClient.CreateIden
}

func (s *Service) UpdateIdentity(ctx context.Context, ID string, bodyID *kClient.UpdateIdentityBody) (*IdentityData, error) {
ctx, span := s.tracer.Start(ctx, "kratos.IdentityApi.UpdateIdentity")
ctx, span := s.tracer.Start(ctx, "kratos.IdentityAPI.UpdateIdentity")
defer span.End()
if ID == "" {
err := fmt.Errorf("no identity ID passed")
Expand Down Expand Up @@ -196,7 +236,7 @@ func (s *Service) UpdateIdentity(ctx context.Context, ID string, bodyID *kClient
}

func (s *Service) DeleteIdentity(ctx context.Context, ID string) (*IdentityData, error) {
ctx, span := s.tracer.Start(ctx, "kratos.IdentityApi.DeleteIdentity")
ctx, span := s.tracer.Start(ctx, "kratos.IdentityAPI.DeleteIdentity")
defer span.End()

rr, err := s.kratos.DeleteIdentityExecute(
Expand All @@ -215,7 +255,7 @@ func (s *Service) DeleteIdentity(ctx context.Context, ID string) (*IdentityData,
return data, err
}

func NewService(kratos kClient.IdentityApi, tracer trace.Tracer, monitor monitoring.MonitorInterface, logger logging.LoggerInterface) *Service {
func NewService(kratos kClient.IdentityAPI, tracer trace.Tracer, monitor monitoring.MonitorInterface, logger logging.LoggerInterface) *Service {
s := new(Service)

s.kratos = kratos
Expand Down
Loading

0 comments on commit 7c2d3f6

Please sign in to comment.