Skip to content

Commit

Permalink
dgtown/ory migrate (#4608)
Browse files Browse the repository at this point in the history
  • Loading branch information
d-g-town authored May 6, 2024
1 parent b1d7344 commit ffb6e9a
Show file tree
Hide file tree
Showing 13 changed files with 305 additions and 1,148 deletions.
169 changes: 169 additions & 0 deletions api/server/handlers/user/migrate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
package user

import (
"errors"
"fmt"
"net/http"
"strconv"
"strings"

"github.com/porter-dev/porter/api/types"

"github.com/porter-dev/porter/internal/models"

ory "github.com/ory/client-go"

"github.com/porter-dev/porter/internal/telemetry"

"github.com/porter-dev/porter/api/server/handlers"
"github.com/porter-dev/porter/api/server/shared"
"github.com/porter-dev/porter/api/server/shared/apierrors"
"github.com/porter-dev/porter/api/server/shared/config"
)

// MigrateUsersHandler migrates users into Ory
type MigrateUsersHandler struct {
handlers.PorterHandler
}

// NewMigrateUsersHandler generates a handler for migrating users
func NewMigrateUsersHandler(
config *config.Config,
decoderValidator shared.RequestDecoderValidator,
writer shared.ResultWriter,
) *MigrateUsersHandler {
return &MigrateUsersHandler{
PorterHandler: handlers.NewDefaultPorterHandler(config, decoderValidator, writer),
}
}

// ServeHTTP migrates users into Ory
func (u *MigrateUsersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ctx, span := telemetry.NewSpan(r.Context(), "serve-migrate-users")
defer span.End()

r = r.Clone(ctx)

thisUser, _ := r.Context().Value(types.UserScope).(*models.User)
if !strings.HasSuffix(thisUser.Email, "@porter.run") {
err := telemetry.Error(ctx, span, nil, "user is not a porter user")
u.HandleAPIError(w, r, apierrors.NewErrForbidden(err))
return
}

users, err := u.Repo().User().ListUsers()
if err != nil {
err := telemetry.Error(ctx, span, nil, "error listing users")
u.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}

var usersMissingAuthMechanism []uint
migrationErrors := map[string][]uint{}

for _, user := range users {
// skip users that are already migrated
if user.AuthProvider == models.AuthProvider_Ory && user.ExternalId != "" {
continue
}

createIdentityBody := ory.CreateIdentityBody{
SchemaId: "preset://email",
Traits: map[string]interface{}{"email": user.Email},
}

if user.EmailVerified {
createIdentityBody.VerifiableAddresses = []ory.VerifiableIdentityAddress{
{
Value: user.Email,
Verified: true,
Via: "email",
Status: "completed",
},
}
}

switch {
case user.Password != "":
password := user.Password
createIdentityBody.Credentials = &ory.IdentityWithCredentials{
Oidc: nil,
Password: &ory.IdentityWithCredentialsPassword{
Config: &ory.IdentityWithCredentialsPasswordConfig{
HashedPassword: &password,
},
},
AdditionalProperties: nil,
}
case user.GithubUserID != 0:
createIdentityBody.Credentials = &ory.IdentityWithCredentials{
Oidc: &ory.IdentityWithCredentialsOidc{
Config: &ory.IdentityWithCredentialsOidcConfig{
Config: nil,
Providers: []ory.IdentityWithCredentialsOidcConfigProvider{
{
Provider: "github",
Subject: strconv.Itoa(int(user.GithubUserID)),
},
},
},
},
}
case user.GoogleUserID != "":
createIdentityBody.Credentials = &ory.IdentityWithCredentials{
Oidc: &ory.IdentityWithCredentialsOidc{
Config: &ory.IdentityWithCredentialsOidcConfig{
Config: nil,
Providers: []ory.IdentityWithCredentialsOidcConfigProvider{
{
Provider: "google",
Subject: user.GoogleUserID,
},
},
},
},
}
default:
usersMissingAuthMechanism = append(usersMissingAuthMechanism, user.ID)
continue
}

createdIdentity, _, err := u.Config().Ory.IdentityAPI.CreateIdentity(u.Config().OryApiKeyContextWrapper(ctx)).CreateIdentityBody(createIdentityBody).Execute()
if err != nil {
errString := fmt.Sprintf("error creating identity: %s", err.Error())
if len(migrationErrors[err.Error()]) == 0 {
migrationErrors[errString] = []uint{}
}
migrationErrors[errString] = append(migrationErrors[errString], user.ID)

continue
}

user.AuthProvider = models.AuthProvider_Ory
user.ExternalId = createdIdentity.Id

_, err = u.Repo().User().UpdateUser(user)
if err != nil {
errString := fmt.Sprintf("error updating user: %s", err.Error())
if len(migrationErrors[err.Error()]) == 0 {
migrationErrors[errString] = []uint{}
}
migrationErrors[errString] = append(migrationErrors[errString], user.ID)
continue
}
}

var errs []error
if len(usersMissingAuthMechanism) > 0 {
errs = append(errs, fmt.Errorf("users missing auth mechanism: %v", usersMissingAuthMechanism))
}
for errString, userIds := range migrationErrors {
errs = append(errs, fmt.Errorf("%s: %v", errString, userIds))
}

if len(errs) > 0 {
err := telemetry.Error(ctx, span, errors.Join(errs...), "error migrating users")
u.HandleAPIError(w, r, apierrors.NewErrPassThroughToClient(err, http.StatusInternalServerError))
return
}
}
25 changes: 25 additions & 0 deletions api/server/router/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,5 +472,30 @@ func getUserRoutes(
Router: r,
})

// Get /api/users/migrate -> user.NewMigrateUsersHandler
migrateUsersEndpoint := factory.NewAPIEndpoint(
&types.APIRequestMetadata{
Verb: types.APIVerbGet,
Method: types.HTTPVerbGet,
Path: &types.Path{
Parent: basePath,
RelativePath: "/users/migrate",
},
Scopes: []types.PermissionScope{types.UserScope},
},
)

migrateUsersHandler := user.NewMigrateUsersHandler(
config,
factory.GetDecoderValidator(),
factory.GetResultWriter(),
)

routes = append(routes, &router.Route{
Endpoint: migrateUsersEndpoint,
Handler: migrateUsersHandler,
Router: r,
})

return routes
}
6 changes: 6 additions & 0 deletions api/server/shared/config/config.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package config

import (
"context"

"github.com/gorilla/sessions"
ory "github.com/ory/client-go"
"github.com/porter-dev/api-contracts/generated/go/porter/v1/porterv1connect"
"github.com/porter-dev/porter/api/server/shared/apierrors/alerter"
"github.com/porter-dev/porter/api/server/shared/config/env"
Expand Down Expand Up @@ -120,6 +123,9 @@ type Config struct {
EnableCAPIProvisioner bool

TelemetryConfig telemetry.TracerConfig

Ory ory.APIClient
OryApiKeyContextWrapper func(ctx context.Context) context.Context
}

type ConfigLoader interface {
Expand Down
4 changes: 4 additions & 0 deletions api/server/shared/config/env/envconfs.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ type ServerConf struct {
TelemetryName string `env:"TELEMETRY_NAME"`
// TelemetryCollectorURL is the URL (host:port) for collecting spans
TelemetryCollectorURL string `env:"TELEMETRY_COLLECTOR_URL,default=localhost:4317"`

OryEnabled bool `env:"ORY_ENABLED,default=false"`
OryUrl string `env:"ORY_URL,default=http://localhost:4000"`
OryApiKey string `env:"ORY_API_KEY"`
}

// DBConf is the database configuration: if generated from environment variables,
Expand Down
16 changes: 16 additions & 0 deletions api/server/shared/config/loader/loader.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package loader

import (
"context"
"encoding/base64"
"errors"
"fmt"
Expand All @@ -11,6 +12,7 @@ import (
"strconv"

gorillaws "github.com/gorilla/websocket"
ory "github.com/ory/client-go"
"github.com/porter-dev/api-contracts/generated/go/porter/v1/porterv1connect"
"github.com/porter-dev/porter/api/server/shared/apierrors/alerter"
"github.com/porter-dev/porter/api/server/shared/config"
Expand Down Expand Up @@ -389,6 +391,20 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
}
res.Logger.Info().Msg("Created billing manager")

c := ory.NewConfiguration()
c.Servers = ory.ServerConfigurations{{
URL: InstanceEnvConf.ServerConf.OryUrl,
}}

if InstanceEnvConf.ServerConf.OryEnabled {
res.Logger.Info().Msg("Creating Ory client")
res.Ory = *ory.NewAPIClient(c)
res.OryApiKeyContextWrapper = func(ctx context.Context) context.Context {
return context.WithValue(ctx, ory.ContextAccessToken, InstanceEnvConf.ServerConf.OryApiKey)
}
res.Logger.Info().Msg("Created Ory client")
}

return res, nil
}

Expand Down
18 changes: 10 additions & 8 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/porter-dev/porter
go 1.20

require (
cloud.google.com/go v0.110.0 // indirect
cloud.google.com/go v0.110.2 // indirect
github.com/AlecAivazis/survey/v2 v2.2.9
github.com/Masterminds/semver/v3 v3.2.0
github.com/aws/aws-sdk-go v1.44.160
Expand Down Expand Up @@ -45,11 +45,11 @@ require (
github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.10.0
github.com/stretchr/testify v1.8.4
golang.org/x/crypto v0.19.0
golang.org/x/net v0.21.0
golang.org/x/oauth2 v0.8.0
google.golang.org/api v0.114.0
github.com/stretchr/testify v1.9.0
golang.org/x/crypto v0.21.0
golang.org/x/net v0.22.0
golang.org/x/oauth2 v0.18.0
google.golang.org/api v0.126.0
google.golang.org/genproto v0.0.0-20230530153820-e85fd2cbaebc
google.golang.org/grpc v1.57.0
google.golang.org/protobuf v1.33.0
Expand Down Expand Up @@ -86,6 +86,7 @@ require (
github.com/matryer/is v1.4.0
github.com/nats-io/nats.go v1.24.0
github.com/open-policy-agent/opa v0.44.0
github.com/ory/client-go v1.9.0
github.com/porter-dev/api-contracts v0.2.157
github.com/riandyrn/otelchi v0.5.1
github.com/santhosh-tekuri/jsonschema/v5 v5.0.1
Expand All @@ -104,7 +105,7 @@ require (
)

require (
cloud.google.com/go/compute v1.19.1 // indirect
cloud.google.com/go/compute v1.20.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
cloud.google.com/go/longrunning v0.4.1 // indirect
github.com/Azure/azure-sdk-for-go v65.0.0+incompatible // indirect
Expand Down Expand Up @@ -149,6 +150,7 @@ require (
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang-jwt/jwt v3.2.1+incompatible // indirect
github.com/google/gnostic v0.6.9 // indirect
github.com/google/s2a-go v0.1.4 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect
github.com/gosimple/unidecode v1.0.1 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 // indirect
Expand Down Expand Up @@ -268,7 +270,7 @@ require (
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/google/uuid v1.3.0
github.com/googleapis/gax-go/v2 v2.7.1 // indirect
github.com/googleapis/gax-go/v2 v2.11.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/gosuri/uitable v0.0.4 // indirect
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
Expand Down
Loading

0 comments on commit ffb6e9a

Please sign in to comment.