Rephrasing the <same question> that <@U05041N85HR>...
# talk-keto
c
Rephrasing the same question that @chilly-scientist-84712 has put. Can we have a the name spaces, relationships and their permissions defined dynamically? One way could be, to rewrite the keto yml file with the new namespace so that Keto would reload the file. But this approach would involve additional layer of generating the namespace definition code programmatically. Is there a better way of achieving this?
c
I am doing this with the SDK. It's pretty slow though, so I wouldn't recommend you do it often. Here's a code example:
Copy code
func (oc *oryClient) authenticate(ctx context.Context) error {
	flow, _, err := oc.FrontendApi.CreateNativeLoginFlow(ctx).Execute()
	if err != nil {
		return err
	}
	login, _, err := oc.FrontendApi.UpdateLoginFlow(ctx).Flow(flow.Id).UpdateLoginFlowBody(ory.UpdateLoginFlowBody{
		UpdateLoginFlowWithPasswordMethod: ory.NewUpdateLoginFlowWithPasswordMethod(oc.nsContext.user, "password", oc.nsContext.password),
	}).Execute()
	if err != nil {
		return err
	}
	oc.token = *login.SessionToken

	return nil
}

func (oc *oryClient) patchProject(ctx context.Context, patch []ory.JsonPatch) (*ory.SuccessfulProjectUpdate, *gohttp.Response, error) {
	return oc.ProjectApi.PatchProject(oc.AuthCtx(ctx), oc.nsContext.projectId).JsonPatch(patch).Execute()
}

func (oc *oryClient) UpdateNamespaces(ctx context.Context, namespaces map[string]int) (*ory.SuccessfulProjectUpdate, error) {
	out := make([]Namespace, len(namespaces))
	var i int
	for name, id := range namespaces {
		out[i] = Namespace{
			Id:   id,
			Name: name,
		}
		i++
	}
	patch := []ory.JsonPatch{
		{
			Op:   OpReplace,
			Path: "/services/permission/config/" + NamespacesKey,
			// We may have to encode something here, to like json or yaml, who knows?
			Value: out,
		},
	}
	success, res, err := oc.patchProject(ctx, patch)
	if res != nil && res.StatusCode == gohttp.StatusUnauthorized {
		if err := oc.authenticate(ctx); err != nil {
			return nil, err
		}
		success, res, err = oc.patchProject(ctx, patch)
	}

	return success, oc.appendMessageIfError(res, err)
}
Mind you, this replaces the list of namespaces entirely, so you will need to fetch existing namespaces first, append your new one to the list, and replace the entire existing list with the new one
This is how I list the namespaces first:
Copy code
func (oc *oryClient) getProject(ctx context.Context) (*ory.Project, *gohttp.Response, error) {
	return oc.ProjectApi.GetProject(oc.AuthCtx(ctx), oc.nsContext.projectId).Execute()
}

func (oc *oryClient) ListNamespaces(ctx context.Context) (map[string]int, int, error) {
	proj, res, err := oc.getProject(ctx)
	if res != nil && res.StatusCode == gohttp.StatusUnauthorized {
		if err := oc.authenticate(ctx); err != nil {
			return nil, 0, err
		}
		proj, res, err = oc.getProject(ctx)
	}
	if err != nil {
		return nil, 0, oc.appendMessageIfError(res, err)
	}
	namespacesConfig := proj.Services.Permission.GetConfig()[NamespacesKey]
	namespaces, ok := namespacesConfig.([]interface{})
	if !ok {
		return nil, 0, fmt.Errorf("unable to assert namespace config as []interface{}")
	}
	var last int
	out := make(map[string]int, len(namespaces))
	for _, ns := range namespaces {
		namespace, ok := ns.(map[string]interface{})
		if !ok {
			return nil, 0, fmt.Errorf("unable to assert namespace as map[string]interface{}")
		}
		id := int(namespace["id"].(float64))
		name := namespace["name"].(string)
		out[name] = id
		if id > last {
			last = id
		}
	}

	return out, last, nil
}
I do this in two operations, so that the consumer of the ory client can decide what to do with the namespaces, ie they can call oryClient.ListNamespaces() first, then mutate the list in whatever way they want, and if changes are to be made, they simply pass the result to oryClient.UpdateNamespaces, which will then replace the old one with the new. Mind you that this is obviously not concurrency safe 🙂
Also, mind you the ProjectApi does not support the PAT that the rest of the ory api's do, you actually need to log in using the ory native login flow and "real" credentials - like the .authenticate() method does above
Oh, you also need to do some magic with the URL you connect to, here is the code (it's kinda hacky) that instanciates my client:
Copy code
const NamespacesKey = "namespaces"

type Namespace struct {
	Id   int    `json:"id" yaml:"id"`
	Name string `json:"name" yaml:"name"`
}

type oryNamespacesContext struct {
	user      string
	password  string
	projectId string
}

// This interface is kind of a hack, but the kind people at Ory
// have blessed me to solve this hurdle in this manner :)
type NamespaceClient interface {
	ListNamespaces(ctx context.Context) (map[string]int, int, error)
	UpdateNamespaces(ctx context.Context, namespaces map[string]int) (*ory.SuccessfulProjectUpdate, error)
}

func NewOryNamespaceClient(url *url.URL, user, password, projectId string, httpClient http.Client) NamespaceClient {
	c := ory.NewConfiguration()
	kratosUrl, _ := url.Parse("")
	kratosUrl.Host = "project." + kratosUrl.Host
	url.Host = "api." + url.Host
	c.Servers = ory.ServerConfigurations{{URL: url.String()}}
	c.HTTPClient = httpClient.(*gohttp.Client)
	c.OperationServers["FrontendApiService.CreateNativeLoginFlow"] = ory.ServerConfigurations{{URL: kratosUrl.String()}}
	c.OperationServers["FrontendApiService.UpdateLoginFlow"] = ory.ServerConfigurations{{URL: kratosUrl.String()}}

	return &oryClient{
		ory.NewAPIClient(c),
		"",
		&oryNamespacesContext{
			user:      user,
			password:  password,
			projectId: projectId,
		},
	}
}
b
godspeed
m
Thanks so much for sharing this HC 🙇 Let me know if that helps @cold-motorcycle-49371 @narrow-van-43826 what do you think of this approach?
c
@curved-fountain-46946, I'm extremely sorry that I didn't reply for the thread. I was out of this experiment for a while. We were using Java, and thought to wrap my mind around before sending a sensible reply. Thanks a lot for the reply! I might have to do something similar in Java. I assume the eventual magic is done by the
PatchProject
which updates the config as we're introducing dynamic entities.
c
That assumption is correct 😌
c
Cool. Thank you! And thanks a lot!