Skip to content

Commit

Permalink
Add support for Audit Logs
Browse files Browse the repository at this point in the history
  • Loading branch information
StefanBratanov committed Sep 4, 2024
1 parent 702e55f commit d2dcac2
Show file tree
Hide file tree
Showing 18 changed files with 839 additions and 4 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ ChatCompletion chatCompletion = chatClient.createChatCompletion(createChatComple
| [Project Users](https://platform.openai.com/docs/api-reference/project-users) | ✔️ |
| [Project Service Accounts](https://platform.openai.com/docs/api-reference/project-service-accounts) | ✔️ |
| [Project API Keys](https://platform.openai.com/docs/api-reference/project-api-keys) | ✔️ |
| [Audit Logs](https://platform.openai.com/docs/api-reference/audit-logs) | |
| [Audit Logs](https://platform.openai.com/docs/api-reference/audit-logs) | ✔️ |

> **_NOTE:_** Legacy APIs are not supported
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/io/github/stefanbratanov/jvm/openai/AuditLog.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.github.stefanbratanov.jvm.openai;

/** A log of a user action or configuration change within this organization. */
public record AuditLog(
String id, String type, long effectiveAt, Project project, Actor actor, AuditLogEvent event) {

/** The project that the action was scoped to. Absent for actions not scoped to projects. */
public record Project(String id, String name) {}

/** The actor who performed the audit logged action. */
public record Actor(String type, Session session, ApiKey apiKey) {

/** The session in which the audit logged action was performed. */
public record Session(User user, String ipAddress) {
/** The user who performed the audit logged action. */
public record User(String id, String email) {}
}

/** The API Key used to perform the audit logged action. */
public record ApiKey(String id, String type, User user, ServiceAccount serviceAccount) {
/** The user who performed the audit logged action. */
public record User(String id, String email) {}

/** The service account that performed the audit logged action. */
public record ServiceAccount(String id) {}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package io.github.stefanbratanov.jvm.openai;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.ApiKeyCreatedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.ApiKeyDeletedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.ApiKeyUpdatedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.InviteAcceptedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.InviteDeletedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.InviteSentEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.LoginFailedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.LogoutFailedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.OrganizationUpdatedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.ProjectArchivedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.ProjectCreatedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.ProjectUpdatedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.ServiceAccountCreatedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.ServiceAccountDeletedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.ServiceAccountUpdatedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.UserAddedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.UserDeletedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.UserUpdatedEvent;
import java.io.IOException;

public class AuditLogDeserializer extends StdDeserializer<AuditLog> {

public AuditLogDeserializer() {
super(AuditLog.class);
}

@Override
public AuditLog deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
JsonNode node = p.getCodec().readTree(p);

String id = node.get("id").asText();
String type = node.get("type").asText();
long effectiveAt = node.get("effective_at").asLong();
JsonNode projectNode = node.get("project");
AuditLog.Project project =
projectNode != null ? p.getCodec().treeToValue(projectNode, AuditLog.Project.class) : null;
JsonNode actorNode = node.get("actor");
AuditLog.Actor actor = p.getCodec().treeToValue(actorNode, AuditLog.Actor.class);

AuditLogEvent event;

switch (type) {
case Constants.API_KEY_CREATED_EVENT_TYPE -> {
JsonNode eventNode = node.get(Constants.API_KEY_CREATED_EVENT_TYPE);
event = p.getCodec().treeToValue(eventNode, ApiKeyCreatedEvent.class);
}
case Constants.API_KEY_UPDATED_EVENT_TYPE -> {
JsonNode eventNode = node.get(Constants.API_KEY_UPDATED_EVENT_TYPE);
event = p.getCodec().treeToValue(eventNode, ApiKeyUpdatedEvent.class);
}
case Constants.API_KEY_DELETED_EVENT_TYPE -> {
JsonNode eventNode = node.get(Constants.API_KEY_DELETED_EVENT_TYPE);
event = p.getCodec().treeToValue(eventNode, ApiKeyDeletedEvent.class);
}
case Constants.INVITE_SENT_EVENT_TYPE -> {
JsonNode eventNode = node.get(Constants.INVITE_SENT_EVENT_TYPE);
event = p.getCodec().treeToValue(eventNode, InviteSentEvent.class);
}
case Constants.INVITE_ACCEPTED_EVENT_TYPE -> {
JsonNode eventNode = node.get(Constants.INVITE_ACCEPTED_EVENT_TYPE);
event = p.getCodec().treeToValue(eventNode, InviteAcceptedEvent.class);
}
case Constants.INVITE_DELETED_EVENT_TYPE -> {
JsonNode eventNode = node.get(Constants.INVITE_DELETED_EVENT_TYPE);
event = p.getCodec().treeToValue(eventNode, InviteDeletedEvent.class);
}
case Constants.LOGIN_FAILED_EVENT_TYPE -> {
JsonNode eventNode = node.get(Constants.LOGIN_FAILED_EVENT_TYPE);
event = p.getCodec().treeToValue(eventNode, LoginFailedEvent.class);
}
case Constants.LOGOUT_FAILED_EVENT_TYPE -> {
JsonNode eventNode = node.get(Constants.LOGOUT_FAILED_EVENT_TYPE);
event = p.getCodec().treeToValue(eventNode, LogoutFailedEvent.class);
}
case Constants.ORGANIZATION_UPDATED_EVENT_TYPE -> {
JsonNode eventNode = node.get(Constants.ORGANIZATION_UPDATED_EVENT_TYPE);
event = p.getCodec().treeToValue(eventNode, OrganizationUpdatedEvent.class);
}
case Constants.PROJECT_CREATED_EVENT_TYPE -> {
JsonNode eventNode = node.get(Constants.PROJECT_CREATED_EVENT_TYPE);
event = p.getCodec().treeToValue(eventNode, ProjectCreatedEvent.class);
}
case Constants.PROJECT_UPDATED_EVENT_TYPE -> {
JsonNode eventNode = node.get(Constants.PROJECT_UPDATED_EVENT_TYPE);
event = p.getCodec().treeToValue(eventNode, ProjectUpdatedEvent.class);
}
case Constants.PROJECT_ARCHIVED_EVENT_TYPE -> {
JsonNode eventNode = node.get(Constants.PROJECT_ARCHIVED_EVENT_TYPE);
event = p.getCodec().treeToValue(eventNode, ProjectArchivedEvent.class);
}
case Constants.SERVICE_ACCOUNT_CREATED_EVENT_TYPE -> {
JsonNode eventNode = node.get(Constants.SERVICE_ACCOUNT_CREATED_EVENT_TYPE);
event = p.getCodec().treeToValue(eventNode, ServiceAccountCreatedEvent.class);
}
case Constants.SERVICE_ACCOUNT_UPDATED_EVENT_TYPE -> {
JsonNode eventNode = node.get(Constants.SERVICE_ACCOUNT_UPDATED_EVENT_TYPE);
event = p.getCodec().treeToValue(eventNode, ServiceAccountUpdatedEvent.class);
}
case Constants.SERVICE_ACCOUNT_DELETED_EVENT_TYPE -> {
JsonNode eventNode = node.get(Constants.SERVICE_ACCOUNT_DELETED_EVENT_TYPE);
event = p.getCodec().treeToValue(eventNode, ServiceAccountDeletedEvent.class);
}
case Constants.USER_ADDED_EVENT_TYPE -> {
JsonNode eventNode = node.get(Constants.USER_ADDED_EVENT_TYPE);
event = p.getCodec().treeToValue(eventNode, UserAddedEvent.class);
}
case Constants.USER_UPDATED_EVENT_TYPE -> {
JsonNode eventNode = node.get(Constants.USER_UPDATED_EVENT_TYPE);
event = p.getCodec().treeToValue(eventNode, UserUpdatedEvent.class);
}
case Constants.USER_DELETED_EVENT_TYPE -> {
JsonNode eventNode = node.get(Constants.USER_DELETED_EVENT_TYPE);
event = p.getCodec().treeToValue(eventNode, UserDeletedEvent.class);
}
default -> event = null;
}

return new AuditLog(id, type, effectiveAt, project, actor, event);
}
}
105 changes: 105 additions & 0 deletions src/main/java/io/github/stefanbratanov/jvm/openai/AuditLogEvent.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package io.github.stefanbratanov.jvm.openai;

import io.github.stefanbratanov.jvm.openai.AuditLogEvent.ApiKeyCreatedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.ApiKeyDeletedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.ApiKeyUpdatedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.InviteAcceptedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.InviteDeletedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.InviteSentEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.LoginFailedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.LogoutFailedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.OrganizationUpdatedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.ProjectArchivedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.ProjectCreatedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.ProjectUpdatedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.ServiceAccountCreatedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.ServiceAccountDeletedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.ServiceAccountUpdatedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.UserAddedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.UserDeletedEvent;
import io.github.stefanbratanov.jvm.openai.AuditLogEvent.UserUpdatedEvent;
import java.util.List;

/** The details for the different types of audit log events */
public sealed interface AuditLogEvent
permits ApiKeyCreatedEvent,
ApiKeyUpdatedEvent,
ApiKeyDeletedEvent,
InviteSentEvent,
InviteAcceptedEvent,
InviteDeletedEvent,
LoginFailedEvent,
LogoutFailedEvent,
OrganizationUpdatedEvent,
ProjectCreatedEvent,
ProjectUpdatedEvent,
ProjectArchivedEvent,
ServiceAccountCreatedEvent,
ServiceAccountUpdatedEvent,
ServiceAccountDeletedEvent,
UserAddedEvent,
UserUpdatedEvent,
UserDeletedEvent {

record ApiKeyCreatedEvent(String id, Data data) implements AuditLogEvent {
public record Data(List<String> scopes) {}
}

record ApiKeyUpdatedEvent(String id, ChangesRequested changesRequested) implements AuditLogEvent {
public record ChangesRequested(List<String> scopes) {}
}

record ApiKeyDeletedEvent(String id) implements AuditLogEvent {}

record InviteSentEvent(String id, Data data) implements AuditLogEvent {
public record Data(String email, String role) {}
}

record InviteAcceptedEvent(String id) implements AuditLogEvent {}

record InviteDeletedEvent(String id) implements AuditLogEvent {}

record LoginFailedEvent(String errorCode, String errorMessage) implements AuditLogEvent {}

record LogoutFailedEvent(String errorCode, String errorMessage) implements AuditLogEvent {}

record OrganizationUpdatedEvent(String id, ChangesRequested changesRequested)
implements AuditLogEvent {
public record ChangesRequested(
String title, String description, String name, Settings settings) {
public record Settings(String threadsUiVisibility, String usageDashboardVisibility) {}
}
}

record ProjectCreatedEvent(String id, Data data) implements AuditLogEvent {
public record Data(String name, String title) {}
}

record ProjectUpdatedEvent(String id, ChangesRequested changesRequested)
implements AuditLogEvent {
public record ChangesRequested(String title) {}
}

record ProjectArchivedEvent(String id) implements AuditLogEvent {}

record ServiceAccountCreatedEvent(String id, Data data) implements AuditLogEvent {
public record Data(String role) {}
}

record ServiceAccountUpdatedEvent(String id, ChangesRequested changesRequested)
implements AuditLogEvent {
public record ChangesRequested(String role) {}
}

record ServiceAccountDeletedEvent(String id) implements AuditLogEvent {}

record UserAddedEvent(String id, Data data) implements AuditLogEvent {
public record Data(String role) {}
}

record UserUpdatedEvent(String id, ChangesRequested changesRequested) implements AuditLogEvent {
public record ChangesRequested(String role) {}
}

record UserDeletedEvent(String id) implements AuditLogEvent {}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package io.github.stefanbratanov.jvm.openai;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import java.io.IOException;

public class AuditLogSerializer extends StdSerializer<AuditLog> {

public AuditLogSerializer() {
super(AuditLog.class);
}

@Override
public void serialize(AuditLog value, JsonGenerator gen, SerializerProvider provider)
throws IOException {
gen.writeStartObject();

gen.writeStringField("id", value.id());
gen.writeStringField("type", value.type());
gen.writeNumberField("effective_at", value.effectiveAt());

if (value.project() != null) {
gen.writeObjectField("project", value.project());
}

if (value.actor() != null) {
gen.writeObjectField("actor", value.actor());
}

AuditLogEvent event = value.event();
if (event != null) {
switch (value.type()) {
case Constants.API_KEY_CREATED_EVENT_TYPE ->
gen.writeObjectField(Constants.API_KEY_CREATED_EVENT_TYPE, event);
case Constants.API_KEY_UPDATED_EVENT_TYPE ->
gen.writeObjectField(Constants.API_KEY_UPDATED_EVENT_TYPE, event);
case Constants.API_KEY_DELETED_EVENT_TYPE ->
gen.writeObjectField(Constants.API_KEY_DELETED_EVENT_TYPE, event);
case Constants.INVITE_SENT_EVENT_TYPE ->
gen.writeObjectField(Constants.INVITE_SENT_EVENT_TYPE, event);
case Constants.INVITE_ACCEPTED_EVENT_TYPE ->
gen.writeObjectField(Constants.INVITE_ACCEPTED_EVENT_TYPE, event);
case Constants.INVITE_DELETED_EVENT_TYPE ->
gen.writeObjectField(Constants.INVITE_DELETED_EVENT_TYPE, event);
case Constants.LOGIN_FAILED_EVENT_TYPE ->
gen.writeObjectField(Constants.LOGIN_FAILED_EVENT_TYPE, event);
case Constants.LOGOUT_FAILED_EVENT_TYPE ->
gen.writeObjectField(Constants.LOGOUT_FAILED_EVENT_TYPE, event);
case Constants.ORGANIZATION_UPDATED_EVENT_TYPE ->
gen.writeObjectField(Constants.ORGANIZATION_UPDATED_EVENT_TYPE, event);
case Constants.PROJECT_CREATED_EVENT_TYPE ->
gen.writeObjectField(Constants.PROJECT_CREATED_EVENT_TYPE, event);
case Constants.PROJECT_UPDATED_EVENT_TYPE ->
gen.writeObjectField(Constants.PROJECT_UPDATED_EVENT_TYPE, event);
case Constants.PROJECT_ARCHIVED_EVENT_TYPE ->
gen.writeObjectField(Constants.PROJECT_ARCHIVED_EVENT_TYPE, event);
case Constants.SERVICE_ACCOUNT_CREATED_EVENT_TYPE ->
gen.writeObjectField(Constants.SERVICE_ACCOUNT_CREATED_EVENT_TYPE, event);
case Constants.SERVICE_ACCOUNT_UPDATED_EVENT_TYPE ->
gen.writeObjectField(Constants.SERVICE_ACCOUNT_UPDATED_EVENT_TYPE, event);
case Constants.SERVICE_ACCOUNT_DELETED_EVENT_TYPE ->
gen.writeObjectField(Constants.SERVICE_ACCOUNT_DELETED_EVENT_TYPE, event);
case Constants.USER_ADDED_EVENT_TYPE ->
gen.writeObjectField(Constants.USER_ADDED_EVENT_TYPE, event);
case Constants.USER_UPDATED_EVENT_TYPE ->
gen.writeObjectField(Constants.USER_UPDATED_EVENT_TYPE, event);
case Constants.USER_DELETED_EVENT_TYPE ->
gen.writeObjectField(Constants.USER_DELETED_EVENT_TYPE, event);
default -> throw new IllegalArgumentException("Unexpected event type: " + value.type());
}
}

gen.writeEndObject();
}
}
Loading

0 comments on commit d2dcac2

Please sign in to comment.