Skip to content
This repository has been archived by the owner on May 13, 2024. It is now read-only.

Commit

Permalink
feat: list server stores
Browse files Browse the repository at this point in the history
  • Loading branch information
le-yams committed May 2, 2024
1 parent 67bfff0 commit 45ba7a4
Show file tree
Hide file tree
Showing 40 changed files with 1,207 additions and 391 deletions.
44 changes: 44 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@

# Changelog


## next

* list server stores

**migration** :

If you already have configured servers with a previous version of this plugin you need to edit the `openfga-servers.xml` configuration file.

This file is located in the `options` folder of your JetBrains IDE application data folder.

* on windows it should be `%AppData%\JetBrains\<YourIDE>\options\openfga-servers.xml`
* on linux it could be `~/.var/app/<your ide related subfolders>/options/openfga-servers.xml`

Open the `openfga-servers.xml` file and rename all the `Server` elements to `ServerState`.

## v0.2.2

* validate openfga cli configuration
* format generated authorization model json file


## v0.2.1

* only display OpenFGA actions group on OpenFGA files


## v0.2.0

* add server oidc configuration support
* add "test connection" in server dialog to check the configuration (checking a list stores api call doesn't fail)
* improve error handling for authorization model json generation (also the action is disabled if required cli is not configured)


## v0.1.0

* DSL syntax support (associated with `.fga` and `.openfga` file extensions)
* Authorization model dsl file template
* Authorization model dsl live templates
* Generate json file from DSL (requires [OpenFGA CLI](https://github.com/openfga/cli) to be installed)
* Configure servers in OpenFGA tool window
29 changes: 0 additions & 29 deletions src/main/java/com/github/le_yams/openfga4intellij/Notifier.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
public interface OpenFGAIcons {

Icon FILE = IconLoader.getIcon("/icons/openfga-color-transparent-16x16.svg", OpenFGAIcons.class);
Icon SERVER_NODE = IconLoader.getIcon("/icons/openfga-color-transparent-16x16.svg", OpenFGAIcons.class);
Icon STORE_NODE = IconLoader.getIcon("/icons/openfga-store.svg", OpenFGAIcons.class);
Icon TOOL_WINDOW = IconLoader.getIcon("/icons/tool-window.svg", OpenFGAIcons.class);

}
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.github.le_yams.openfga4intellij.cli.tasks;

import com.github.le_yams.openfga4intellij.Notifier;
import com.github.le_yams.openfga4intellij.cli.CliProcess;
import com.github.le_yams.openfga4intellij.cli.CliProcessTask;
import com.github.le_yams.openfga4intellij.cli.CliTaskException;
import com.github.le_yams.openfga4intellij.util.notifications.Notifier;
import com.github.le_yams.openfga4intellij.util.notifications.ProjectNotifier;
import com.github.le_yams.openfga4intellij.settings.OpenFGASettingsState;
import com.intellij.codeInsight.actions.ReformatCodeProcessor;
import com.intellij.openapi.application.ApplicationManager;
Expand Down Expand Up @@ -32,6 +33,7 @@ public class DslToJsonTask extends Task.Backgroundable implements CliProcessTask
private final PsiFile dslFile;
private final Path targetPath;
private final CliProcess process;
private final Notifier notifier;

public static Optional<DslToJsonTask> create(@NotNull PsiFile dslFile, @NotNull Path dslFilePath) {
var targetName = computeJsonGeneratedFileName(dslFile);
Expand All @@ -52,6 +54,7 @@ private DslToJsonTask(@NotNull PsiFile dslFile, @NotNull Path dslFilePath, @NotN
super(dslFile.getProject(), "Generating json model for " + dslFile.getName(), true);
this.dslFile = dslFile;
this.targetPath = targetPath;
notifier = new ProjectNotifier(dslFile.getProject());

process = new CliProcess(
OpenFGASettingsState.getInstance().requireCli(),
Expand All @@ -74,7 +77,7 @@ public void run(@NotNull ProgressIndicator indicator) {
try {
process.start(indicator, this);
} catch (CliTaskException e) {
notifyError(dslFile, e.getMessage());
notifier.notifyError("Error generating json authorization model", e);
}
}

Expand All @@ -84,11 +87,11 @@ public void onCancel() {
}

@Override
public Void onSuccess(File stdOutFile, File stdErrFile) throws IOException, CliTaskException {
public Void onSuccess(File stdOutFile, File stdErrFile) throws IOException {
Files.copy(stdOutFile.toPath(), targetPath, StandardCopyOption.REPLACE_EXISTING);
ApplicationManager.getApplication().invokeLater(
() -> show(new GeneratedFile(dslFile.getProject(), targetPath)),
ModalityState.NON_MODAL);
ModalityState.nonModal());
return null;
}

Expand All @@ -102,10 +105,6 @@ public CliTaskException onFailure(File stdOutFile, File stdErrFile) {
}
}

private static void notifyError(PsiFile psiFile, String message) {
Notifier.notifyError(psiFile.getProject(), "Error generating json authorization model", message);
}

private void show(GeneratedFile generatedFile) {
generatedFile.refreshInTreeView();
generatedFile.openInEditor();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package com.github.le_yams.openfga4intellij.sdk;

import com.github.le_yams.openfga4intellij.servers.model.Server;
import com.github.le_yams.openfga4intellij.servers.util.ServersUtil;
import dev.openfga.sdk.api.model.Store;
import dev.openfga.sdk.errors.FgaInvalidParameterException;

import java.util.List;
import java.util.concurrent.CompletableFuture;

public interface OpenFgaApiClient {

CompletableFuture<List<Store>> listStores();

static OpenFgaApiClient ForServer(Server server) {
try {
var openFgaClient = ServersUtil.createClient(server);
return new SdkClient(openFgaClient);
} catch (FgaInvalidParameterException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.github.le_yams.openfga4intellij.sdk;

import dev.openfga.sdk.api.client.OpenFgaClient;
import dev.openfga.sdk.api.client.model.ClientListStoresResponse;
import dev.openfga.sdk.api.configuration.ClientListStoresOptions;
import dev.openfga.sdk.api.model.Store;
import dev.openfga.sdk.errors.FgaInvalidParameterException;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;

class SdkClient implements OpenFgaApiClient {

private static final int STORES_PAGE_SIZE = 50;

private final OpenFgaClient fgaClient;

public SdkClient(OpenFgaClient fgaClient) {
this.fgaClient = fgaClient;
}

@Override
public CompletableFuture<List<Store>> listStores() {
return listStores(new ClientListStoresOptions().pageSize(STORES_PAGE_SIZE));
}

private CompletableFuture<List<Store>> listStores(ClientListStoresOptions options) {
try {
return fgaClient.listStores(options).thenCompose(this::listNextStores);
} catch (FgaInvalidParameterException e) {
throw new RuntimeException(e.getMessage(), e);
}
}

private CompletableFuture<List<Store>> listNextStores(ClientListStoresResponse response) {
var continuationToken = response.getContinuationToken();
if (continuationToken == null || continuationToken.isBlank()) {
return CompletableFuture.completedFuture(response.getStores());
}

try {
return fgaClient
.listStores(new ClientListStoresOptions().pageSize(STORES_PAGE_SIZE).continuationToken(continuationToken))
.thenCompose((ClientListStoresResponse nextResponse) ->
listNextStores(nextResponse).thenApply(nextStores -> Stream.concat(response.getStores().stream(), nextStores.stream()).toList()));
} catch (FgaInvalidParameterException e) {
throw new RuntimeException(e.getMessage(), e);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ public record Oidc(
String clientSecret,
String scope
) {
public static final Oidc EMPTY = new Oidc("", "", "", "");
}
Original file line number Diff line number Diff line change
@@ -1,62 +1,28 @@
package com.github.le_yams.openfga4intellij.servers.model;

import com.intellij.credentialStore.CredentialAttributes;
import com.intellij.credentialStore.CredentialAttributesKt;
import com.intellij.credentialStore.Credentials;
import com.intellij.ide.passwordSafe.PasswordSafe;
import org.jetbrains.annotations.NotNull;

import java.util.Objects;
import java.util.UUID;

public class Server {
public final class Server {

private String id;
private String name;
private String url;
private AuthenticationMethod authenticationMethod = AuthenticationMethod.NONE;
private String apiToken;
private Oidc oidc = Oidc.EMPTY;

public Server() {
this("new server");
}

public Server(String name) {
this.id = UUID.randomUUID().toString();
this.name = name;
}

public String loadUrl() {
var credentials = getCredentials(CredentialKey.URL);
if (credentials == null) {
return "";
}
return credentials.getPasswordAsString();
}

public void storeUrl(String url) {
var attributes = getCredentialAttributes(CredentialKey.URL);
PasswordSafe.getInstance().set(attributes, new Credentials(id, url));
}

public String loadApiToken() {
var credentials = getCredentials(CredentialKey.API_TOKEN);
if (credentials == null) {
return "";
}
return credentials.getPasswordAsString();
}

public void storeApiToken(String token) {
var attributes = getCredentialAttributes(CredentialKey.API_TOKEN);
PasswordSafe.getInstance().set(attributes, new Credentials(id, token));
}

public Credentials getCredentials(String keySuffix) {
CredentialAttributes attributes = getCredentialAttributes(keySuffix);
return PasswordSafe.getInstance().get(attributes);
}

@NotNull
private CredentialAttributes getCredentialAttributes(String keySuffix) {
var key = id + "_" + keySuffix;
return new CredentialAttributes(CredentialAttributesKt.generateServiceName("OpenFGAServer", key));
public Server(String id, String name, String url, AuthenticationMethod authenticationMethod, String apiToken, Oidc oidc) {
this.id = id;
this.name = name;
this.url = url;
this.authenticationMethod = authenticationMethod;
this.apiToken = apiToken;
this.oidc = oidc;
}

public String getId() {
Expand All @@ -75,48 +41,56 @@ public void setName(String name) {
this.name = name;
}

public String getUrl() {
return url;
}

public void setUrl(String url) {
this.url = url;
}

public AuthenticationMethod getAuthenticationMethod() {
return authenticationMethod;
}

public void setAuthenticationMethod(AuthenticationMethod authenticationMethod) {
if (authenticationMethod == null) {
authenticationMethod = AuthenticationMethod.NONE;
}
this.authenticationMethod = authenticationMethod;
}

public Oidc loadOidc() {
var credentials = getCredentials(CredentialKey.OIDC_CLIENT);
var clientId = credentials != null ? credentials.getUserName() : "";
var clientSecret = credentials != null ? credentials.getPasswordAsString() : "";

credentials = getCredentials(CredentialKey.OIDC_TOKEN_ENDPOINT);
var tokenEndpoint = credentials != null ? credentials.getPasswordAsString() : "";
public String getApiToken() {
return apiToken;
}

credentials = getCredentials(CredentialKey.OIDC_SCOPE);
var audience = credentials != null ? credentials.getPasswordAsString() : "";
public void setApiToken(String apiToken) {
this.apiToken = apiToken;
}

return new Oidc(tokenEndpoint, clientId, clientSecret, audience);
public Oidc getOidc() {
return oidc;
}

public void storeOidc(Oidc oidc) {
var attributes = getCredentialAttributes(CredentialKey.OIDC_CLIENT);
PasswordSafe.getInstance().set(attributes, new Credentials(oidc.clientId(), oidc.clientSecret()));
attributes = getCredentialAttributes(CredentialKey.OIDC_TOKEN_ENDPOINT);
PasswordSafe.getInstance().set(attributes, new Credentials(id, oidc.tokenEndpoint()));
attributes = getCredentialAttributes(CredentialKey.OIDC_SCOPE);
PasswordSafe.getInstance().set(attributes, new Credentials(id, oidc.scope()));
public void setOidc(Oidc oidc) {
this.oidc = oidc;
}

@Override
public String toString() {
return name;
}

private interface CredentialKey {
@Override
public boolean equals(Object object) {
if (this == object) return true;
if (object == null || getClass() != object.getClass()) return false;
Server that = (Server) object;
return Objects.equals(id, that.id);
}

String URL = "url";
String API_TOKEN = "apiToken";
String OIDC_CLIENT = "oidc_client";
String OIDC_TOKEN_ENDPOINT = "oidc_token_endpoint";
String OIDC_SCOPE = "oidc_scope";
@Override
public int hashCode() {
return Objects.hash(id);
}
}
Loading

0 comments on commit 45ba7a4

Please sign in to comment.