codex: Add ability to update user permissions
- Active - Registered - Disabled
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "10.0.0",
|
||||
"version": "10.0.100",
|
||||
"rollForward": "latestMinor"
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,7 @@
|
||||
<ItemGroup>
|
||||
<Compile Include="../Shared/Remoting.fs" />
|
||||
<Compile Include="Types.fs" />
|
||||
<Compile Include="Utils.fs" />
|
||||
<Compile Include="Extensions.fs" />
|
||||
<Compile Include="Remoting.fs" />
|
||||
<Compile Include="Map.fs" />
|
||||
|
||||
@@ -6,17 +6,47 @@ open Feliz
|
||||
|
||||
[<Erase>]
|
||||
type OpenFGA =
|
||||
[<ReactComponent>]
|
||||
static member private Spinner() =
|
||||
JSX.html
|
||||
$"""
|
||||
import {{ Spinner }} from "@fluentui/react-components";
|
||||
|
||||
<Spinner size="tiny" />
|
||||
"""
|
||||
|
||||
[<ReactComponent>]
|
||||
static member Checkbox(key, label: string, user: string, relation: string, object: string) =
|
||||
let isLoading, setLoading = React.useState false
|
||||
let isChecked, setChecked = React.useState false
|
||||
|
||||
let handleChange (ev: Types.Event) =
|
||||
console.debug("[OpenFGA.Checkbox] Checkbox %s changed to %o", key, not isChecked)
|
||||
console.debug("[OpenFGA.Checkbox] Checkbox %s for user %s rel %s changed to %o", key, user, relation, not isChecked)
|
||||
// TODO: Write to OpenFGA
|
||||
setChecked(not isChecked)
|
||||
let newChecked = not isChecked
|
||||
// setChecked
|
||||
setLoading true
|
||||
Remoting.adminApi.setUserPermissions {
|
||||
User = user
|
||||
Permissions = [|
|
||||
{ Name = relation; Enabled = newChecked }
|
||||
|]
|
||||
}
|
||||
|> Async.StartAsPromise
|
||||
|> Promise.iter (fun res ->
|
||||
match res with
|
||||
| Ok () ->
|
||||
console.debug("Success.")
|
||||
setChecked newChecked
|
||||
| Error err ->
|
||||
console.error("Error: %s", err)
|
||||
setLoading false
|
||||
)
|
||||
|
||||
React.useEffect (
|
||||
(fun () ->
|
||||
setLoading true
|
||||
|
||||
Remoting.openFgaApi.Check {
|
||||
User = user
|
||||
Relation = relation
|
||||
@@ -30,6 +60,8 @@ type OpenFGA =
|
||||
setChecked hasRelation
|
||||
| Error err ->
|
||||
console.error("[OpenFGA.Checkbox] Error checking user %s has relation %s to %s", user, relation, object)
|
||||
|
||||
setLoading false
|
||||
)
|
||||
),
|
||||
[| |]
|
||||
@@ -38,6 +70,9 @@ type OpenFGA =
|
||||
Html.div [
|
||||
prop.classes [ "flex-row-center" ]
|
||||
prop.children [
|
||||
if isLoading then
|
||||
OpenFGA.Spinner() |> Utils.toReact
|
||||
else
|
||||
Html.input [
|
||||
prop.id (sprintf "openfga-checkbox-%s" key)
|
||||
prop.type'.checkbox
|
||||
@@ -51,4 +86,3 @@ type OpenFGA =
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
7
src/Codex/src/Client/Utils.fs
Normal file
7
src/Codex/src/Client/Utils.fs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace Oceanbox.Codex
|
||||
|
||||
module Utils =
|
||||
open Fable.Core
|
||||
open Feliz
|
||||
|
||||
let toReact (el: JSX.Element) : ReactElement = unbox el
|
||||
@@ -528,22 +528,70 @@ module Admin =
|
||||
return ()
|
||||
}
|
||||
|
||||
let setUserPermissions (ctx: HttpContext) (req: Remoting.UserPermissionRequest) =
|
||||
async {
|
||||
let user = ctx.User.Identity.Name
|
||||
let logger = ctx.GetLogger<Remoting.Api.Admin> ()
|
||||
do logger.LogInformation ("setUserPermissions from {User}: {@Req}", user, req)
|
||||
// TODO(simkir): Sanitize/check the request, aka turn the dto to an internal type
|
||||
try
|
||||
let fga = ctx.GetService<OpenFgaClient> ()
|
||||
let writes: Model.ClientWriteRequest =
|
||||
req.Permissions
|
||||
|> Array.choose (fun permission ->
|
||||
if permission.Enabled then
|
||||
Some {
|
||||
Remoting.Tuple.empty with
|
||||
User = req.User
|
||||
Relation = permission.Name
|
||||
Object = req.User
|
||||
}
|
||||
else
|
||||
None
|
||||
)
|
||||
|> OpenFGA.Queries.write
|
||||
if writes.Writes.Count > 0 then
|
||||
let! fgaWriteResp = fga.Write writes |> Async.AwaitTask
|
||||
do logger.LogInformation ("OpenFGA write responded with: {JSON}", fgaWriteResp.ToJson ())
|
||||
|
||||
let deletes =
|
||||
req.Permissions
|
||||
|> Array.choose (fun permission ->
|
||||
if permission.Enabled then
|
||||
None
|
||||
else
|
||||
Remoting.Tuple.delete(req.User, permission.Name, req.User)
|
||||
|> Some
|
||||
)
|
||||
|> OpenFGA.Queries.deleteTuples
|
||||
if deletes.Count > 0 then
|
||||
let! fgaDeleteResp = fga.DeleteTuples deletes |> Async.AwaitTask
|
||||
do logger.LogInformation ("OpenFGA delete responded with: {JSON}", fgaDeleteResp.ToJson ())
|
||||
|
||||
return Ok ()
|
||||
with e ->
|
||||
do logger.LogError (e, "Error setting user permissions")
|
||||
// TODO: Maybe do not send exn message
|
||||
return Error (sprintf "Error setting user permissions: %s" e.Message)
|
||||
}
|
||||
|
||||
let private impl (ctx: HttpContext) : Remoting.Api.Admin = {
|
||||
addUsers = Handler.addUsers ctx
|
||||
addArchive = Handler.addArchive ctx
|
||||
addArchiveGroups = Handler.addArchiveGroups ctx
|
||||
addGroupPermissions = Handler.addGroupPermissions ctx
|
||||
addUsers = Handler.addUsers ctx
|
||||
deleteArchive = Handler.deleteArchive ctx
|
||||
getAllGroups = Handler.getAllGroups ctx
|
||||
getArchive = Handler.getArchive ctx
|
||||
getArchiveCount = Handler.getArchiveCount ctx
|
||||
getArchiveDataSet = Handler.getArchiveDataSet ctx
|
||||
getArchiveRefs = Handler.getArchiveRefs ctx
|
||||
getArchiveTypes = fun () -> Handler.getArchiveTypes ctx
|
||||
getArchives = Handler.getArchives ctx
|
||||
getDataSets = fun () -> Handler.getAllDataSets ctx
|
||||
getGroupUsers = Handler.getGroupUsers ctx
|
||||
removeUsers = Handler.removeUsers ctx
|
||||
getDataSets = fun () -> Handler.getAllDataSets ctx
|
||||
getArchiveDataSet = Handler.getArchiveDataSet ctx
|
||||
addArchive = Handler.addArchive ctx
|
||||
setUserPermissions = Handler.setUserPermissions ctx
|
||||
updateArchive = Handler.updateArchive ctx
|
||||
}
|
||||
|
||||
|
||||
@@ -71,7 +71,8 @@ module OpenFGA =
|
||||
|> Seq.toArray
|
||||
|> Array.map (fun t ->
|
||||
let condition : Remoting.Condition option =
|
||||
Option.ofObj t.Key.Condition
|
||||
t.Key.Condition
|
||||
|> Option.ofObj
|
||||
|> Option.map (fun cond -> {
|
||||
Name = cond.Name
|
||||
Context = JsonSerializer.Serialize cond.Context
|
||||
@@ -176,7 +177,23 @@ module OpenFGA =
|
||||
|
||||
result
|
||||
|
||||
let delete' (tuples: ClientTupleKey array) =
|
||||
/// To be used with OpenFga.Sdk.Client.OpenFgaClient.DeleteTuples
|
||||
let deleteTuples (tuples: Remoting.Tuple array) : ResizeArray<ClientTupleKeyWithoutCondition> =
|
||||
let deletes: ClientTupleKeyWithoutCondition array =
|
||||
tuples
|
||||
|> Array.map (fun tuple ->
|
||||
let result = ClientTupleKeyWithoutCondition ()
|
||||
|
||||
do result.Object <- tuple.Object
|
||||
do result.Relation <- tuple.Relation
|
||||
do result.User <- tuple.User
|
||||
|
||||
result
|
||||
)
|
||||
|
||||
ResizeArray deletes
|
||||
|
||||
let delete' (tuples: ClientTupleKey array) : ClientWriteRequest =
|
||||
let result = ClientWriteRequest ()
|
||||
|
||||
let deletes: ClientTupleKeyWithoutCondition array =
|
||||
@@ -195,6 +212,22 @@ module OpenFGA =
|
||||
|
||||
result
|
||||
|
||||
/// To be used with OpenFga.Sdk.Client.OpenFgaClient.DeleteTuples
|
||||
let deleteTuples' (tuples: ClientTupleKey array) : ResizeArray<ClientTupleKeyWithoutCondition> =
|
||||
let deletes: ClientTupleKeyWithoutCondition array =
|
||||
tuples
|
||||
|> Array.map (fun tuple ->
|
||||
let result = ClientTupleKeyWithoutCondition ()
|
||||
|
||||
do result.Object <- tuple.Object
|
||||
do result.Relation <- tuple.Relation
|
||||
do result.User <- tuple.User
|
||||
|
||||
result
|
||||
)
|
||||
|
||||
ResizeArray deletes
|
||||
|
||||
let write (tuples: Remoting.Tuple array) =
|
||||
let result = ClientWriteRequest ()
|
||||
|
||||
@@ -310,9 +343,10 @@ module OpenFGA =
|
||||
let logger = ctx.GetLogger<Remoting.Api.OpenFGA> ()
|
||||
let fga = ctx.GetService<OpenFgaClient> ()
|
||||
try
|
||||
let deleteRequest = Queries.delete [| tuple |]
|
||||
do logger.LogInformation ("Delete req: {Request}", deleteRequest.ToJson ())
|
||||
let! resp = fga.Write deleteRequest |> Async.AwaitTask
|
||||
let deleteRequest = Queries.deleteTuples [| tuple |]
|
||||
let json = JsonSerializer.Serialize deleteRequest
|
||||
do logger.LogInformation ("Delete req: {Request}", json)
|
||||
let! resp = fga.DeleteTuples deleteRequest |> Async.AwaitTask
|
||||
do logger.LogInformation ("Delete resp: {Response}", resp.ToJson ())
|
||||
return Ok (resp.Deletes.Count >= 1)
|
||||
with e ->
|
||||
|
||||
@@ -196,26 +196,39 @@ module Remoting =
|
||||
Permissions: ArchiveRelation array
|
||||
}
|
||||
|
||||
[<Struct>]
|
||||
type Permission = {
|
||||
Name: string
|
||||
Enabled: bool
|
||||
}
|
||||
|
||||
[<Struct>]
|
||||
type UserPermissionRequest = {
|
||||
User: string
|
||||
Permissions: Permission array
|
||||
}
|
||||
|
||||
[<RequireQualifiedAccess>]
|
||||
module Api =
|
||||
type Auth = { IsAuthenticated: Async<bool> }
|
||||
|
||||
type Admin = {
|
||||
addUsers: AddUsersRequest -> Async<Result<unit, string>>
|
||||
addArchive: AddArchiveRequest -> Async<Result<Archive, string>>
|
||||
addArchiveGroups: AddArchiveGroupsRequest -> Async<Result<unit, string>>
|
||||
addGroupPermissions: AddGroupPermissionsRequest -> Async<Result<unit, string>>
|
||||
addUsers: AddUsersRequest -> Async<Result<unit, string>>
|
||||
deleteArchive: Archmaester.Dto.ArchiveId -> Async<Result<bool, string>>
|
||||
getAllGroups: Async<string array>
|
||||
getArchive: Archmaester.Dto.ArchiveId -> Async<Result<Archmaester.Dto.ArchiveProps, string>>
|
||||
getArchiveCount: Archmaester.Dto.ArchiveFilter -> Async<Result<int, string>>
|
||||
getArchiveDataSet: System.Guid -> Async<Result<DataSet, string>>
|
||||
getArchiveRefs: Archmaester.Dto.ArchiveFilter -> Async<Result<Archmaester.Dto.ArchiveProps array, string>>
|
||||
getArchiveTypes: unit -> Async<Archmaester.Dto.ArchiveType array>
|
||||
getArchives: int -> int -> Archmaester.Dto.ArchiveFilter -> Async<Result<Archmaester.Dto.ArchiveProps array, string>>
|
||||
getDataSets: unit -> Async<Result<DataSet array, string>>
|
||||
getGroupUsers: string -> Async<string array>
|
||||
removeUsers: string array -> Async<Result<unit, string>>
|
||||
getDataSets: unit -> Async<Result<DataSet array, string>>
|
||||
getArchiveDataSet: System.Guid -> Async<Result<DataSet, string>>
|
||||
addArchive: AddArchiveRequest -> Async<Result<Archive, string>>
|
||||
setUserPermissions: UserPermissionRequest -> Async<Result<unit, string>>
|
||||
updateArchive: System.Guid -> EditArchiveRequest -> Async<Result<Archive, string>>
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user