Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ const (
setPolicies function = "set_policies"
isAuthorizedString function = "is_authorized_string"
isAuthorizedJSON function = "is_authorized_json"
ffi function = "ffi"
)

// exportFuncs returns a map of exported functions from the wasm module.
Expand All @@ -20,6 +21,7 @@ func exportFuncs(module api.Module) map[string]api.Function {
exportedFuncs[string(isAuthorizedJSON)] = module.ExportedFunction(string(isAuthorizedJSON))
exportedFuncs[string(setEntities)] = module.ExportedFunction(string(setEntities))
exportedFuncs[string(setPolicies)] = module.ExportedFunction(string(setPolicies))
exportedFuncs[string(ffi)] = module.ExportedFunction(string(ffi))
// allocate and deallocate help us manage memory in the wasm module.
exportedFuncs[string(allocate)] = module.ExportedFunction(string(allocate))
exportedFuncs[string(deallocate)] = module.ExportedFunction(string(deallocate))
Expand Down
59 changes: 59 additions & 0 deletions ffi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package cedar

import (
"context"
"encoding/json"
"fmt"
)

// FFIResponse is the response from the Foreign Function Interface (FFI) of the cedar_policy library.
type FFIResponse struct {
// IsSuccess is true if the request was successful.
// If false, the Errors field will contain the errors that occurred.
IsSuccess bool `json:"success,string,omitempty"`
RawResult string `json:"result,omitempty"`
// Result is the result of the policy evaluation.
Result EvalResponse `json:"-"`
// IsInternal is true if the request failed due to an internal error.
IsInternal bool `json:"isInternal,omitempty"`
// Errors is the list of errors that occurred during evaluation.
Errors []string `json:"errors,omitempty"`
}

// FFI gives access to the Foreign Function Interface (FFI) of the cedar_policy library.
// See https://docs.rs/cedar-policy/latest/cedar_policy/frontend/is_authorized/fn.json_is_authorized.html for more information.
func (c *CedarEngine) FFI(ctx context.Context, input string) (FFIResponse, error) {
inputSize := uint64(len(input))
inputPtr, err := c.exportedFuncs[string(allocate)].Call(ctx, inputSize)
if err != nil {
return FFIResponse{}, err
}
defer c.exportedFuncs[string(deallocate)].Call(ctx, inputPtr[0], inputSize)
ok := c.module.Memory().WriteString(uint32(inputPtr[0]), input)
if !ok {
return FFIResponse{}, fmt.Errorf("failed to write input to memory")
}
resPtr, err := c.exportedFuncs[string(ffi)].Call(ctx, inputPtr[0], inputSize)
if err != nil {
return FFIResponse{}, err
}
var res FFIResponse
output, err := c.readDecisionFromMemory(ctx, resPtr[0])
if err != nil {
return FFIResponse{}, err
}
err = json.Unmarshal(output, &res)
if err != nil {
return FFIResponse{}, err
}
if !res.IsSuccess {
return res, nil
}
var result EvalResponse
err = json.Unmarshal([]byte(res.RawResult), &result)
if err != nil {
return FFIResponse{}, err
}
res.Result = result
return res, nil
}
85 changes: 85 additions & 0 deletions ffi_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package cedar

import (
"context"
"testing"
)

func TestCedarEngine_FFI(t *testing.T) {
ctx := context.Background()
engine, err := NewCedarEngine(ctx)
if err != nil {
t.Fatal(err)
}
defer engine.Close(ctx)
t.Run("ffi must return deny", func(t *testing.T) {
ffiMustReturnDeny(t, engine, `
{
"principal": "User::\"alice\"",
"action": "Photo::\"view\"",
"resource": "Photo::\"photo\"",
"slice": {
"policies": {},
"entities": []
},
"context": {}
}`)
})
t.Run("ffi must return error if json is not serializable", func(t *testing.T) {
ffiMustReturnErrorIfJsonIsNotSerializable(t, engine, `
{
"principal": "User::\"alice\"",
"action": "Photo::\"view\"",
}`)
})
t.Run("ffi must return allow", func(t *testing.T) {
ffiMustReturnAllow(t, engine, `
{
"context": {},
"slice": {
"policies": {
"001": "permit(principal, action, resource);"
},
"entities": [],
"templates": {},
"template_instantiations": []
},
"principal": "User::\"alice\"",
"action": "Action::\"view\"",
"resource": "Resource::\"thing\""
}`)
})
}

func ffiMustReturnDeny(t *testing.T, engine *CedarEngine, input string) {
ctx := context.Background()
res, err := engine.FFI(ctx, input)
if err != nil {
t.Fatal(err)
}
if res.Result.Decision.IsPermit() {
t.Fatal("expected Deny")
}
}

func ffiMustReturnErrorIfJsonIsNotSerializable(t *testing.T, engine *CedarEngine, input string) {
ctx := context.Background()
res, err := engine.FFI(ctx, input)
if err != nil {
t.Fatal(err)
}
if len(res.Errors) == 0 {
t.Fatal("expected error")
}
}

func ffiMustReturnAllow(t *testing.T, engine *CedarEngine, input string) {
ctx := context.Background()
res, err := engine.FFI(ctx, input)
if err != nil {
t.Fatal(err)
}
if !res.Result.Decision.IsPermit() {
t.Fatal("expected Allow")
}
}
26 changes: 25 additions & 1 deletion lib/src/interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,17 @@ extern crate core;
extern crate wee_alloc;
extern crate serde_json;

use cedar_policy::{PolicySet, Entities, Authorizer, EntityUid, Context, Request, Decision, Response};
use cedar_policy::{
PolicySet,
Entities,
Authorizer,
EntityUid,
Context,
Request,
Decision,
Response,
frontend
};

use std::{slice};
use std::collections::HashMap;
Expand Down Expand Up @@ -134,6 +144,20 @@ pub unsafe extern "C" fn _is_authorized_json(
return ((ptr as u64) << 32) | len as u64;
}

#[cfg_attr(all(target_arch = "wasm32"), export_name = "ffi")]
#[no_mangle]
// Provides FFI support
pub unsafe extern "C" fn _ffi(
payload_ptr: u32,
payload_len: u32,
) -> u64 {
let payload = ptr_to_string(payload_ptr, payload_len);
let result = frontend::is_authorized::json_is_authorized(payload.as_str());
let body = serde_json::to_string(&result).unwrap();
let (ptr, len) = string_to_ptr(&body);
std::mem::forget(body);
return ((ptr as u64) << 32) | len as u64;
}

/// Returns a string from WebAssembly compatible numeric types representing
/// its pointer and length.
Expand Down
Binary file modified static/cedar.wasm
Binary file not shown.