Array.map for Rego or how to combine RBAC with api routes - open-policy-agent

I would like to define permissions in JSON data such as:
"permissions": [
{
"resource": ["users", ":uid", "salary"],
"action": "GET"
}
]
Now when evaluating, I want to replace :uid with input.subject . How would I go about this? Is there something like Array.prototype.map() in Rego?
PS: I know I can do this, for example.
allow {
input.action = ["GET", "POST"][_]
input.resource = ["users", uid, "salary"]
input.subject = uid
}
But instead of spelling out each path in the policy, I would like to use RBAC (roles + permissions) so that I can pass those API endpoint permissions as JSON data. Is it possible?

You can certainly write a policy that scans over all of the permissions and checks if there's a match. Here's a simple (but complete) example:
package play
permissions = [
{
"resource": "/users/:uid/salary",
"action": "GET"
},
{
"resource": "/metrics",
"action": "GET"
}
]
default allow = false
allow {
some p
matching_permission[p]
}
matching_permission[p] {
some p
matching_permission_action[p]
matching_permission_resource[p]
}
matching_permission_action[p] {
some p
permissions[p].action == input.action
}
matching_permission_resource[p] {
some p
path := replace(permissions[p].resource, ":uid", input.subject)
path == input.resource
}
The downside of this approach is that each evaluation has to, in the worse case, scan over all permissions. As more permissions are added, evaluation will take longer. Depending on how large the permission set can get, this might not satisfy the latency requirements.
The typical answer to this is to use partial evaluation to pre-evaluate the permissions data and generate a rule set that can be evaluated in constant-time due to rule indexing. This approach is covered on the Policy Performance page. For example, if you run partial evaluation on this policy, this is the output:
$ opa eval -d play.rego -f pretty 'data.play.allow' -p --disable-inlining data.play.allow
+-----------+-------------------------------------------------------------------------+
| Query 1 | data.partial.play.allow |
+-----------+-------------------------------------------------------------------------+
| Support 1 | package partial.play |
| | |
| | allow { |
| | "GET" = input.action |
| | |
| | replace("/users/:uid/salary", ":uid", input.subject) = input.resource |
| | } |
| | |
| | allow { |
| | "POST" = input.action |
| | |
| | replace("/metrics", ":uid", input.subject) = input.resource |
| | } |
+-----------+-------------------------------------------------------------------------+
In this case, the equality statements would be recognized by the rule indexer. However, the indexer will not be able to efficiently index the ... = input.resource statements due to the replace() call.
Part of the challenge is that this policy is not pure RBAC...it's an attribute-based policy that encodes an equality check (between a path segment and the subject) into the permission data. If we restructure the permission data a little bit, we can workaround this:
package play2
permissions = [
{
"owner": "subject",
"resource": "salary",
"action": "GET"
},
{
"resource": "metrics",
"action": "GET"
},
]
allow {
some p
matching_permission[p]
}
matching_permission[p] {
some p
matching_permission_action[p]
matching_permission_resource[p]
matching_permission_owner[p]
}
matching_permission_action[p] {
some p
permissions[p].action == input.action
}
matching_permission_resource[p] {
some p
permissions[p].resource == input.resource
}
matching_permission_owner[p] {
some p
permissions[p]
not permissions[p].owner
}
matching_permission_owner[p] {
some p
owner := permissions[p].owner
input.owner = input[owner]
}
This version is quite similar except we have explicitly encoded ownership into the permission model. The "owner" field indicates the resource owner (provided in the input document under the "owner" key) must be equal to the specified input value (in this example, input.subject).
Running partial evaluation on this version yields the following output:
$ opa eval -d play2.rego -f pretty 'data.play2.allow' -p --disable-inlining data.play2.allow
+-----------+-------------------------------+
| Query 1 | data.partial.play2.allow |
+-----------+-------------------------------+
| Support 1 | package partial.play2 |
| | |
| | allow { |
| | "GET" = input.action |
| | |
| | "salary" = input.resource |
| | |
| | input.owner = input.subject |
| | } |
| | |
| | allow { |
| | "GET" = input.action |
| | |
| | "metrics" = input.resource |
| | } |
+-----------+-------------------------------+
All of the conditions on the rule bodies are now recognized by the rule indexer and evaluation latency will scale w/ the number of rules that could potentially match the input. The tradeoff here of course is that whenever the permissions change, partial evaluation has to be re-executed.

Related

Rail Query includes + filter by column

I start in Ruby and Ruby on rails. I am trying to modify the access to this object (below) in base via the form and I will like to filter by the values that are in the table node_tags.
def map
......
nodes = Node.bbox(bbox).where(:visible => true).includes(:node_tags).limit(MAX_NUMBER_OF_NODES + 1)
.....
end
Probably on SQL request
SELECT * FROM nodes n INNER JOIN node_tags nt on n.node_id = nt.node_id where nt.k = 'alert' and nt.v = 'true'
Table nodes
| node_id | latitude | longitude | changeset_id | visible | timestamp | tile | version | redaction_id |
|---------|-----------|-----------|--------------|---------|-----------|------|---------|--------------|
| 11 | 473705641 | 3955487 | 11 | TRUE |
| 12 | 473705641 | 3955487 | 12 | TRUE |
table node_tags
| node_id | k | v |
|---------|-------|------|
| 11 | name | bob |
| 12 | alert | true |
If I understand right, you need to get sql as in your example by ORM (ActiveRecord).
Try this:
nodes = Node.where(:visible => true).join(:node_tags).where("node_tags.k = 'alert' and node_tags.v = 'true'")

How do I set up an API request for my login?

I have a login and registration with username and password fields created with Swift. I need to attach it to the API below. When I hit register it should create a new user. When I login, it should create and store the access token. After login or registration, I am taken to a tableview which displays myself (current user) as well as all the other users in the tableView. I can edit or delete the users in the tableView. How do I do this? Ay information about this would be very helpful since I have limited knowledge with REST APIs. That is a fake URI for example. Thank you!
API Reference
URIs relative to: https://myloginapi.com/api/v1 - mock URI
auth.token
Create new access_token for user and returns it.
Request
| Param | Value |
| ------------ | ---------------------------------------------------- |
| Path | `POST` /auth/access_token |
| Headers | ~~(empty)~~ |
| Body | `JSON` {"name":"`(username)`","pass":"`(password)`"} |
Response
| Param | Value |
| ------------ | ---------------------------------------------------- |
| Body | `JSON` {"data":"`access_token`"} |
user.get
Get one user by id and returns its data.
Request
| Param | Value |
| ------------ | ---------------------------------------------------- |
| Path | `GET` /user/`(id)` |
| Headers | Authorization: `(access_token)` |
| Body | ~~(empty)~~ |
Response
| Param | Value |
| ------------ | ---------------------------------------------------- |
| Body | `JSON` {"data":`user`} |
user.update
Update one user by id using request body and returns its data.
Request
| Param | Value |
| ------------ | ---------------------------------------------------- |
| Path | `PUT` /user/`(id)` |
| Headers | Authorization: `(access_token)` |
| Body | `JSON` {"name":"`(username)`","pass":"`(password)`"} |
Response
| Param | Value |
| ------------ | ---------------------------------------------------- |
| Body | `JSON` {"data":`user`} |
user.create
Create one new user using request body and returns its data.
Request
| Param | Value |
| ------------ | ---------------------------------------------------- |
| Path | `POST` /user |
| Headers | ~~(empty)~~ |
| Body | `JSON` {"name":"`(username)`","pass":"`(password)`"} |
Response
| Param | Value |
| ------------ | ---------------------------------------------------- |
| Body | `JSON` {"data":`user`} |
user.delete
Delete one user by id and returns an empty response.
Request
| Param | Value |
| ------------ | ---------------------------------------------------- |
| Path | `DELETE` /user/`(id)` |
| Headers | Authorization: `(access_token)` |
| Body | ~~(empty)~~ |
Response
| Param | Value |
| ------------ | ---------------------------------------------------- |
| Body | ~~(empty)~~ |
user.list
List all users and returns a collection.
Request
| Param | Value |
| ------------ | ---------------------------------------------------- |
| Path | `GET` /user |
| Headers | Authorization: `(access_token)` |
| Body | ~~(empty)~~ |
Response
| Param | Value |
| ------------ | ---------------------------------------------------- |
| Body | `JSON` {"data":[`users`]}
Use Alamofire.
Sample request would be :
Alamofire.request("https://myloginapi.com/api/v1").responseJSON { response in
print(response.request) // original URL request
print(response.response) // HTTP URL response
print(response.data) // server data
print(response.result) // result of response serialization
if let JSON = response.result.value {
print("JSON: \(JSON)")
}
}
For details check their doc: https://github.com/Alamofire/Alamofire
You can use URLSession
let urlconfig = URLSessionConfiguration.default
urlconfig.timeoutIntervalForRequest = 20
urlconfig.timeoutIntervalForResource = 60
let session = Foundation.URLSession(configuration: urlconfig, delegate: self, delegateQueue: OperationQueue.main)
let request = NSMutableURLRequest(url: URL(string:"https://myloginapi.com/api/v1")) // Set Url here
request.httpMethod = "POST" //(can set GET/POST)
request.cachePolicy = URLRequest.CachePolicy.reloadIgnoringCacheData
let paramString = String(format:"name=%#&pass=%#",username,password) //Set Parameter string here
request.httpBody = paramString.data(using: String.Encoding.utf8)
task = session.dataTask(with: request as URLRequest) {
(data, response, error) in
do {
if data == nil
{
//Failure
}
else
{
if let json = try JSONSerialization.jsonObject(with: data!, options: []) as? NSDictionary {
print(json) // Json response from server
}
}
}

Find key value pair in PostgreSQL's HSTORE

Given a table games and column identifiers, whose type is HSTORE:
| id | name | identifiers |
|----|------------------|------------------------------------|
| 1 | Metal Gear | { sku: 109127072, ean: 512312342 } |
| 2 | Theme Hospital | { sku: 399348341 } |
| 3 | Final Fantasy | { ean: 109127072, upc: 999284928 } |
| 4 | Age of Mythology | { tbp: 'a998fa31'} |
| 5 | Starcraft II | { sku: 892937742, upc: 002399488 } |
How can I find if a given set of key-value pairs has at least one match in the database?
For example, if I supply this array: [ {sku: 109127072 }, { upc: 999284928 } ], I should see:
| id | name | identifiers |
|----|----------------|------------------------------------|
| 1 | Metal Gear | { sku: 109127072, ean: 512312342 } |
| 3 | Final Fantasy | { ean: 109127072, upc: 999284928 } |
For rails 5 you, think, should try using or operator like:
h = { sku: 109127072, upc: 999284928 }
rela = Person.where("identifiers -> ? = ?", h.keys.first, h[h.keys.first])
h.keys[1..-1].reduce(rela) {|rela, key| rela.or("identifiers -> ? = ?", key, h[key]) }
# => relation with ored-arguments
for non 5-th rails you shall use arel as descibed here.

Aerospike: lua udf always returns an empty result even if udf return stream without any filtering, etc

Can not understand why aggregateQuery always returns an empty result. Tried to test in aql, the same problem: 0 rows in set.
Indexes are all there.
aql> show indexes
+---------------+-------------+-----------+------------+-------+------------------------------+-------------+------------+-----------+
| ns | bin | indextype | set | state | indexname | path | sync_state | type |
+---------------+-------------+-----------+------------+-------+------------------------------+-------------+------------+-----------+
| "test" | "name" | "NONE" | "profiles" | "RW" | "inx_test_name" | "name" | "synced" | "STRING" |
| "test" | "age" | "NONE" | "profiles" | "RW" | "inx_test_age" | "age" | "synced" | "NUMERIC" |
aql> select * from test.profiles
+---------+-----+
| name | age |
+---------+-----+
| "Sally" | 19 |
| 20 | |
| 22 | |
| 28 | |
| "Ann" | 22 |
| "Bob" | 22 |
| "Tammy" | 22 |
| "Ricky" | 20 |
| 22 | |
| 19 | |
+---------+-----+
10 rows in set (0.026 secs)
aql> AGGREGATE mystream.avg_age() ON test.profiles WHERE age BETWEEN 20 and 29
0 rows in set (0.004 secs)
It seems that you are trying the example here.
There are two problems about the udf script. I paste the code of the lua script :
function avg_age(stream)
local function female(rec)
return rec.gender == "F"
end
local function name_age(rec)
return map{ name=rec.name, age=rec.age }
end
local function eldest(p1, p2)
if p1.age > p2.age then
return p1
else
return p2
end
end
return stream : filter(female) : map(name_age) : reduce(eldest)
end
First, there is no bin named 'gender' in your set, so you got 0 rows after aggregateQuery.
Second, this script isn't doing exactly what the function name 'avg_age' means, it just return the eldest record with name and age.
I paste my code bellow, it just replace the reduce func, and alert the map and filter func to meat the demand. You can just skip the filter process.
function avg_age(stream)
count = 0
sum = 0
local function female(rec)
return true
end
local function name_age(rec)
return rec.age
end
local function avg(p1, p2)
count = count + 1
sum = sum + p2
return sum / count
end
return stream : filter(female) : map(name_age) : reduce(avg)
end
The output looks like bellow :
AGGREGATE mystream.avg_age() ON test.avgage WHERE age BETWEEN 20 and 29
+---------+
| avg_age |
+---------+
| 22 |
+---------+
1 row in set (0.001 secs)

New to F# and Don't Know How to Fix FS0025 error

I'm struggling trying to teach myself F# (my very first functional programming language) by building a very important app (or trying to) that will record user input and write to a JSON file (or eventually, a database) the outcomes of requests for help made by poor trafficking survivors to all the anti-trafficking charities/NGO's in the US. I keep getting this error, after trying everything I could think of to make sure that all possible cases for the pattern in question were considered. I don't know how to fix it, and I really can't use any error suppression on this app. Any help at all would be greatly appreciated, as I have no one else to reach out to for any help and support. Here is my code from the TerminalBuilder.fs file:
namespace Nonprofits
module TerminalBuilder =
open System
open System.IO
open Nonprofits.Types
let rec caller():Caller =
printfn "Are you reporting as an advocate on behalf of someone else or for yourself?"
printfn " 1 for Advocate"
printfn " 2 for Self"
let answer = Console.ReadLine()
match answer with
| "1" -> Advocate
| "2" -> ClientOrVictim
| _ -> printfn "Invalid Entry"
caller()
let specialneeds():Set<Disability> =
let rec spnds(s:Set<Disability>): Set<Disability> =
printfn "Do you/the person on whose behalf you're reporting have any disabling conditions?"
printfn " 1 for Learning Disability"
printfn " 2 for Physical Disability"
printfn " 3 for Mental Health Issues"
printfn " 4 for Substance Addiction Issues"
printfn " 5 for Pregnancy-Related Limitations"
printfn " 6 for Chronic Illness"
printfn " 7 for Don't KNow/Undiagnosed"
printfn " Enter 'Exit' for None"
let answer = Console.ReadLine()
match answer.Trim().ToLower() with
| "exit" -> s
| _ ->
let sn =
match answer.Trim().ToLower() with
| "1" -> Some LearningDisabled
| "2" -> Some PhysicallyDisabled
| "3" -> Some MentalIllness
| "4" -> Some SubstanceAddiction
| "5" -> Some Pregnancy
| "6" -> Some ChronicIllness
| "7" -> Some Undiagnosed
| "exit" -> printfn "No disabling conditions"
None
match sn with
| None -> spnds(s)
| Some (x) -> spnds(s.Add(x))
spnds(new Set<Disability> ([]))
And here is my types file, Nonprofits.fs:
namespace Nonprofits
open System.Collections
module Types =
type NgoType =
| HomelessShelter
| DVShelter
| TraffickingVictimSafehouse
| TraffickingSurvivorAftercare // gamut of legal, housing, clothing, food, medical, reintegration, etc.
| FoodPantries
| ClothingAssistance
| FreeMedicalDentalClinic
type Ngo = Ngo of NgoType * string
type Caller =
| ClientOrVictim
| Advocate
and CallerStatus =
| VictimServicesAdvocate of Caller
| DVvictim of Caller
| SexTraffickingSurvivor of Caller
| HomelessVictim of Caller
| NaturalDisasterVictim of Caller
| GeneralPovertyVictim of Caller
and Disability =
| Pregnancy
| PhysicallyDisabled
| LearningDisabled
| MentalIllness
| SubstanceAddiction
| ChronicIllness
| Undiagnosed
and SpecialNeeds = SpecialNeeds of Set<Disability>
type UnmetNeeds =
| TraffickingSafebed
| DVsafebed
| Housing
| Clothing
| Food
| Legal
| Medical
| Dental
| Vision
| DrugRehab
| TraumaCare
| PsychiatricCare
| SkillsTraining
| EducationHelp
| JobPlacement
| EconomicSupport
type CallerRequest =
| TraffickingVictimAftercare of Set<UnmetNeeds>
| PovertyVictimCare of Set<UnmetNeeds>
type Followup =
| SocialWorkerFollowup of Help
| CallerSelfDirected of Help
and Help =
| Helped //fully helped with everything caller needed
| ExhaustedOptions // exhausted resources and still not helped
| WrongHelp //i.e. caller offered smoking cessation counseling when caller needed sex trafficking aftercare
| NotHelped of Followup
| GivenReferral of ReferredToNextNgo
and ReferredToNextNgo = ReferredToNextNgo of Followup * Ngo
type CallOutcome =
| CallerHelped
| CallerNotHelped of Followup
| CallerReferred of ReferredToNextNgo
type Call = Call of Caller * CallerRequest * CallOutcome
And here is the error I'm getting:
C:\Users\3CU1501060\Documents\Projects\DUandTypesPractice\DUandTypesPractice\TerminalBuilder.fs(27,27): Warning FS0025: Incomplete pattern matches on this expression. For example, the value '"a"' may indicate a case not covered by the pattern(s). (FS0025) (DUandTypesPractice)
when you pattern match
let sn =
match answer.Trim().ToLower() with
| "1" -> Some LearningDisabled
| "2" -> Some PhysicallyDisabled
| "3" -> Some MentalIllness
| "4" -> Some SubstanceAddiction
| "5" -> Some Pregnancy
| "6" -> Some ChronicIllness
| "7" -> Some Undiagnosed
| "exit" -> printfn "No disabling conditions"
None
```
you check "1", "2", ... "exit", but there are other values, for example "a", you need to add another case
| answer -> //do something
in your case maybe
| answer -> printfn "not supported answer %A" answer
None // or retry?
the compiler is helping you, telling you "what happen if user choose 'a'"?

Resources