| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193 |
- 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, resp, err := u.Config().Ory.IdentityAPI.CreateIdentity(u.Config().OryApiKeyContextWrapper(ctx)).CreateIdentityBody(createIdentityBody).Execute()
- if err != nil {
- switch resp.StatusCode {
- // identity already exists, so we need to list the identities and find the one that matches
- case 409:
- identities, _, err := u.Config().Ory.IdentityAPI.ListIdentities(u.Config().OryApiKeyContextWrapper(ctx)).CredentialsIdentifier(user.Email).Execute()
- if err != nil {
- errString := fmt.Sprintf("error calling list identities``: %v\n", err)
- if len(migrationErrors[err.Error()]) == 0 {
- migrationErrors[errString] = []uint{}
- }
- migrationErrors[errString] = append(migrationErrors[errString], user.ID)
- continue
- }
- if len(identities) != 1 {
- errString := fmt.Sprintf("expected 1 identity, got %d", len(identities))
- if len(migrationErrors[err.Error()]) == 0 {
- migrationErrors[errString] = []uint{}
- }
- migrationErrors[errString] = append(migrationErrors[errString], user.ID)
- continue
- }
- createdIdentity = &identities[0]
- default:
- 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
- }
- }
|