orchestracities / anubis Goto Github PK
View Code? Open in Web Editor NEWAnubis: a flexible policy enforcement solution for NGSI APIs (and beyond!)
Home Page: https://anubis-pep.readthedocs.org
License: Apache License 2.0
Anubis: a flexible policy enforcement solution for NGSI APIs (and beyond!)
Home Page: https://anubis-pep.readthedocs.org
License: Apache License 2.0
import express from "express"
import bp from "body-parser"
import { createLibp2p } from 'libp2p'
import { TCP } from '@libp2p/tcp'
import { WebSockets } from '@libp2p/websockets'
import { Mplex } from '@libp2p/mplex'
import { Noise } from '@chainsafe/libp2p-noise'
import { CID } from 'multiformats/cid'
import { KadDHT } from '@libp2p/kad-dht'
import all from 'it-all'
import delay from 'delay'
import { Bootstrap } from '@libp2p/bootstrap'
import * as json from 'multiformats/codecs/json'
import { sha256 } from 'multiformats/hashes/sha2'
import { FloodSub } from '@libp2p/floodsub'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { MulticastDNS } from '@libp2p/mdns'
import axios from 'axios'
import fs from 'fs'
import { Multiaddr } from "@multiformats/multiaddr";
import dns from "dns/promises";
import cors from 'cors';
// Configuration for the port used by this node
const server_port = process.env.SERVER_PORT || 8099
// Uri of the Anubis API connected to this middleware
const anubis_api_uri = process.env.ANUBIS_API_URI || "127.0.0.1:8085"
// The multiaddress format address this middleware listens on
const listen_address = process.env.LISTEN_ADDRESS || '/dnsaddr/localhost/tcp/49662'
// Is this a private organisation? (Private org won't share policies that aren't of a specific user only)
const is_private_org = process.env.IS_PRIVATE_ORG || "true"
// Convert DNS address to IP address (solves Docker issues)
var listen_ma = new Multiaddr(listen_address)
var options = listen_ma.toOptions()
if(listen_address.includes("dnsaddr") && options.host != 'localhost') {
const lookup = await dns.lookup(options.host)
listen_ma = new Multiaddr(listen_ma.toString().replace(options.host, lookup.address).replace("dnsaddr", "ip4"))
}
// Setting up Node app
var app = express()
app.use(cors())
app.use(bp.json())
app.use(bp.urlencoded({ extended: true }))
// Keeping track of resources being provided
var providedResources = []
// Setting up the Libp2p node
const node = await createLibp2p({
addresses: {
listen: [listen_ma]
},
transports: [new TCP(), new WebSockets()],
streamMuxers: [new Mplex()],
connectionEncryption: [new Noise()],
dht: new KadDHT(),
peerDiscovery: [
new MulticastDNS({
interval: 20e3
})
],
connectionManager: {
autoDial: true
},
pubsub: new FloodSub(),
relay: {
enabled: true,
hop: {
enabled: true
},
advertise: {
enabled: true,
}
}
})
// Endpoint to retrieve node metadata
app.get('/metadata', async(req, res) => {
res.json({"policy_api_uri": anubis_api_uri})
})
// Endpoint for receiving resources from the mobile app
app.post('/resource/mobile/retrieve', async(req, res) => {
if (!Object.keys(req.body).length) {
return res.status(400).json({
message: "Request body cannot be empty",
})
}
var { resource, service, servicepath } = req.body
if (!resource) {
res.status(400).json({
message: "Ensure you sent a resource field",
})
}
if (!service) {
res.status(400).json({
message: "Ensure you sent a service field",
})
}
if (!servicepath) {
res.status(400).json({
message: "Ensure you sent a servicepath field",
})
}
// TODO: Email of the user
var responseData = [
{
"usrMail": "[email protected]",
"usrData": [
{
"id": resource,
"description": "test",
"children": []
}
]
}
]
const bytes = json.encode({ resource: resource })
const hash = await sha256.digest(bytes)
const cid = CID.create(1, json.code, hash)
var providers = []
try {
providers = await all(node.contentRouting.findProviders(cid, { timeout: 3000 }))
}
catch(error) {
res.end(`No providers for ${resource}`)
return
}
for (const provider of providers) {
var providerPolicyApi = null
await axios({
method: 'get',
url: `http://${provider.multiaddrs[0].nodeAddress().address}:8098/metadata`
})
.then(async function (response) {
providerPolicyApi = response.data["policy_api_uri"]
})
.catch(function (error) {
console.log(`Can't retrieve policy API URL for provider ${provider.multiaddrs[0].nodeAddress().address}`)
})
if(!providerPolicyApi) {
continue
}
await axios({
method: 'get',
url: `http://${providerPolicyApi}/v1/policies`,
headers: {
'fiware-Service': service,
'fiware-Servicepath': servicepath
},
params: {
'resource': resource
}
})
.then(async function (response) {
for (const policy_entry of response.data) {
if(is_private_org != "true") {
var filtered_agents = policy_entry.agent.filter(a => !a.includes("acl:agent:"))
if(filtered_agents.length > 0) {
continue
}
}
responseData[0].usrData[0].children.push({"id": policy_entry["id"], "actorType": policy_entry["agent"], "mode": policy_entry["mode"]})
}
})
.catch(function (error) {
console.log(error.response.data)
})
}
res.json({responseData})
})
// Endpoint for providing a resource from the mobile app
app.post('/resource/mobile/send', async(req, res) => {
if (!Object.keys(req.body).length) {
return res.status(400).json({
message: "Request body cannot be empty",
})
}
var { policies, service, servicepath } = req.body
if (!policies) {
res.status(400).json({
message: "Ensure you sent a policies field",
})
}
if (!service) {
res.status(400).json({
message: "Ensure you sent a service field",
})
}
if (!servicepath) {
res.status(400).json({
message: "Ensure you sent a servicepath field",
})
}
for(const entry of policies) {
const usrMail = entry.usrMail
for(const resource of entry.usrData) {
const resId = resource.id
for(const policy of resource.children) {
var modes = []
for(const m of policy.mode.split(",")) {
modes.push(m)
}
modes = modes.filter(e => e != '')
var new_policy = {
"id": policy.id,
"access_to": resId,
"resource_type": "mobile",
"mode": modes,
"agent": [
policy.actorType
]
}
var message = {
"action": "send_mobile",
"policy": new_policy,
"service": service,
"servicepath": servicepath,
}
message = JSON.stringify(message)
await node.pubsub.publish(resId, uint8ArrayFromString(message)).catch(err => {
console.error(err)
res.end(`Error: ${err}`)
})
}
}
}
res.json({})
})
// Endpoint for providing a resource
app.post('/resource/provide', async(req, res) => {
if (!Object.keys(req.body).length) {
return res.status(400).json({
message: "Request body cannot be empty",
})
}
var { resource } = req.body
if (!resource) {
res.status(400).json({
message: "Ensure you sent a resource field",
})
}
const bytes = json.encode({ resource: resource })
const hash = await sha256.digest(bytes)
const cid = CID.create(1, json.code, hash)
await node.contentRouting.provide(cid)
providedResources.push(resource)
console.log(`Provided policy for resource ${resource}`)
res.end(`Provided policy for resource ${resource}`)
})
// Endpoint for subscribing to a resource topic
app.post('/resource/subscribe', async(req, res) => {
if (!Object.keys(req.body).length) {
return res.status(400).json({
message: "Request body cannot be empty",
})
}
var { resource, policy, service, servicepath } = req.body
if (!resource) {
res.status(400).json({
message: "Ensure you sent a resource field",
})
}
if (!service) {
res.status(400).json({
message: "Ensure you sent a service field",
})
}
if (!servicepath) {
res.status(400).json({
message: "Ensure you sent a servicepath field",
})
}
const topics = await node.pubsub.getTopics()
if (!topics.includes(resource)) {
await node.pubsub.subscribe(resource)
console.log(`Subscribed to ${resource}`)
}
const bytes = json.encode({ resource: resource })
const hash = await sha256.digest(bytes)
const cid = CID.create(1, json.code, hash)
var providers = []
try {
providers = await all(node.contentRouting.findProviders(cid, { timeout: 3000 }))
}
catch(error) {
res.end(`Subscribed to ${resource}, no other providers found`)
return
}
console.log(`Syncing with other providers for ${resource}...`)
for (const provider of providers) {
var providerPolicyApi = null
await axios({
method: 'get',
url: `http://${provider.multiaddrs[0].nodeAddress().address}:8098/metadata`
})
.then(async function (response) {
providerPolicyApi = response.data["policy_api_uri"]
})
.catch(function (error) {
console.log(`Can't retrieve policy API URL for provider ${provider.multiaddrs[0].nodeAddress().address}`)
})
if(!providerPolicyApi) {
continue
}
await axios({
method: 'post',
url: `http://${anubis_api_uri}/v1/tenants/`,
data: {"name": service}
})
.then(async function (response) {
console.log(`Created Tenant ${service}`)
})
.catch(function (error) {
console.log(`No new Tenant created`)
})
await axios({
method: 'get',
url: `http://${providerPolicyApi}/v1/policies`,
headers: {
'fiware-Service': service,
'fiware-Servicepath': servicepath
},
params: {
'resource': resource
}
})
.then(async function (response) {
for (const policy_entry of response.data) {
// TODO: Concurrency
if(is_private_org != "true") {
var filtered_agents = policy_entry.agent.filter(a => !a.includes("acl:agent:"))
if(filtered_agents.length > 0) {
continue
}
}
await axios({
method: 'post',
url: `http://${anubis_api_uri}/v1/policies`,
headers: {
'fiware-Service': service,
'fiware-Servicepath': servicepath
},
data: policy_entry
})
.then(function (r) {
console.log(r.data)
})
.catch(function (err) {
console.log(err.response.data)
})
}
})
.catch(function (error) {
console.log(error.response.data)
})
}
res.end(`Subscribed to ${resource}`)
})
// Endpoint when a new policy is created
app.post('/resource/policy/new', async(req, res) => {
if (!Object.keys(req.body).length) {
return res.status(400).json({
message: "Request body cannot be empty",
})
}
var { resource, policy, service, servicepath } = req.body
if (!resource) {
res.status(400).json({
message: "Ensure you sent a resource field",
})
}
if (!policy) {
res.status(400).json({
message: "Ensure you sent a policy field",
})
}
if (!service) {
res.status(400).json({
message: "Ensure you sent a service field",
})
}
if (!servicepath) {
res.status(400).json({
message: "Ensure you sent a servicepath field",
})
}
var message = {
"action": "post",
"policy_id": policy,
"service": service,
"servicepath": servicepath,
}
message = JSON.stringify(message)
await node.pubsub.publish(resource, uint8ArrayFromString(message)).catch(err => {
console.error(err)
res.end(`Error: ${err}`)
})
res.end("Policy message sent: " + message)
console.log("Policy message sent: " + message)
})
// Endpoint when a policy is updated
app.post('/resource/policy/update', async(req, res) => {
if (!Object.keys(req.body).length) {
return res.status(400).json({
message: "Request body cannot be empty",
})
}
var { resource, policy, service, servicepath } = req.body
if (!resource) {
res.status(400).json({
message: "Ensure you sent a resource field",
})
}
if (!policy) {
res.status(400).json({
message: "Ensure you sent a policy field",
})
}
if (!service) {
res.status(400).json({
message: "Ensure you sent a service field",
})
}
if (!servicepath) {
res.status(400).json({
message: "Ensure you sent a servicepath field",
})
}
var message = {
"action": "put",
"policy_id": policy,
"service": service,
"servicepath": servicepath,
}
message = JSON.stringify(message)
await node.pubsub.publish(resource, uint8ArrayFromString(message)).catch(err => {
console.error(err)
res.end(`Error: ${err}`)
})
res.end("Policy message sent: " + message)
console.log("Policy message sent: " + message)
})
// Endpoint when a policy is deleted
app.post('/resource/policy/delete', async(req, res) => {
if (!Object.keys(req.body).length) {
return res.status(400).json({
message: "Request body cannot be empty",
})
}
var { resource, policy, service, servicepath } = req.body
if (!resource) {
res.status(400).json({
message: "Ensure you sent a resource field",
})
}
if (!policy) {
res.status(400).json({
message: "Ensure you sent a policy field",
})
}
if (!service) {
res.status(400).json({
message: "Ensure you sent a service field",
})
}
if (!servicepath) {
res.status(400).json({
message: "Ensure you sent a servicepath field",
})
}
var message = {
"action": "delete",
"policy_id": policy,
"service": service,
"servicepath": servicepath,
}
message = JSON.stringify(message)
await node.pubsub.publish(resource, uint8ArrayFromString(message)).catch(err => {
console.error(err)
res.end(`Error: ${err}`)
})
res.end("Policy message sent: " + message)
console.log("Policy message sent: " + message)
})
// Function to process a message arriving on a topic (resource)
async function processTopicMessage(evt) {
const sender = await node.peerStore.addressBook.get(evt.detail.from)
const message = JSON.parse(uint8ArrayToString(evt.detail.data))
console.log(`Node received: ${uint8ArrayToString(evt.detail.data)} on topic ${evt.detail.topic}`)
if(message.action == "send_mobile") {
await axios({
method: 'post',
url: `http://${anubis_api_uri}/v1/tenants/`,
data: {"name": message.service}
})
.then(async function (response) {
console.log(`Created Tenant ${message.service}`)
})
.catch(function (error) {
console.log(error)
})
await axios({
method: 'post',
url: `http://${anubis_api_uri}/v1/policies`,
headers: {
'fiware-Service': message.service,
'fiware-Servicepath': message.servicepath
},
data: message.policy
})
.then(function (r) {
console.log(r)
})
.catch(function (err) {
console.log(err.response.data)
})
return
}
var providerPolicyApi = null
await axios({
method: 'get',
url: `http://${sender[0].multiaddr.nodeAddress().address}:8098/metadata`
})
.then(async function (response) {
providerPolicyApi = response.data["policy_api_uri"]
})
.catch(function (error) {
console.log(error)
})
if(message.action == "delete") {
await axios({
method: 'delete',
url: `http://${anubis_api_uri}/v1/policies/${message.policy_id}`,
headers: {
'fiware-Service': message.service,
'fiware-Servicepath': message.servicepath
},
data: response.data
})
.then(function (r) {
console.log(r)
})
.catch(function (err) {
console.log(err)
})
return
}
await axios({
method: 'get',
url: `http://${providerPolicyApi}/v1/policies/${message.policy_id}`,
headers: {
'fiware-Service': message.service,
'fiware-Servicepath': message.servicepath
}
})
.then(async function (response) {
if(message.action == "post") {
await axios({
method: 'post',
url: `http://${anubis_api_uri}/v1/tenants/`,
data: {"name": message.service}
})
.then(async function (response) {
console.log(`Created Tenant ${resource}`)
})
.catch(function (error) {
console.log(error)
})
await axios({
method: 'post',
url: `http://${anubis_api_uri}/v1/policies`,
headers: {
'fiware-Service': message.service,
'fiware-Servicepath': message.servicepath
},
data: response.data
})
.then(function (r) {
console.log(r)
})
.catch(function (err) {
console.log(err.response.data)
})
}
else if(message.action == "put") {
await axios({
method: 'put',
url: `http://${anubis_api_uri}/v1/policies/${message.policy_id}`,
headers: {
'fiware-Service': message.service,
'fiware-Servicepath': message.servicepath
},
data: response.data
})
.then(function (r) {
console.log(r)
})
.catch(function (err) {
console.log(err)
})
}
})
.catch(function (error) {
console.log(error)
})
}
// Saving config to file
async function saveConfiguration() {
var persistentDataFileStream = fs.createWriteStream('data.json')
const topics = await node.pubsub.getTopics()
let data2 = JSON.stringify({"topics": topics, "resources": providedResources})
persistentDataFileStream.write(data2)
persistentDataFileStream.close()
}
// Starting server
var server = app.listen(server_port, async() => {
await node.start()
try {
let rawdata = fs.readFileSync('data.json')
let data = JSON.parse(rawdata)
for(const resource of data.resources) {
providedResources.push(resource)
const bytes = json.encode({ resource: resource })
const hash = await sha256.digest(bytes)
const cid = CID.create(1, json.code, hash)
try {
await node.contentRouting.provide(cid)
}
catch(err) {
console.log(`Failed to initially provide ${resource}`)
}
}
for(const topic of data.topics) {
node.pubsub.subscribe(topic)
}
await saveConfiguration()
}
catch(err) {
console.log("Couldn't read any initial config")
}
console.log("Node started with:")
node.getMultiaddrs().forEach((ma) => console.log(`${ma.toString()}`))
node.connectionManager.addEventListener('peer:connect', (evt) => {
const connection = evt.detail
console.log('Connection established to:', connection.remotePeer.toString())
})
node.addEventListener('peer:discovery', async(evt) => {
const peer = evt.detail
if (node.peerId.toString() == peer.id.toString()) {
return
}
var peerId = node.peerStore.addressBook.get(peer.id)
if (!peerId) {
console.log('Discovered:', peer.id.toString())
node.peerStore.addressBook.set(peer.id, peer.multiaddrs)
node.dial(peer.id)
}
})
await node.pubsub.addEventListener("message", (evt) => processTopicMessage(evt))
await delay(1000)
console.log(node.peerId.toString())
var host = server.address().address
var port = server.address().port
console.log("App listening at http://%s:%s", host, port)
})
Is your feature request related to a problem? Please describe.
How can install the python package?
Describe the solution you'd like
Document pypi package installation / location
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
Add acl:origin
support for policies, to allow for requests to be validated based on their HTTP origin rather than the agent's name/group/class
Describe the solution you'd like
Add acl:origin
as a valid agentType, and add the rego rules to handle it.
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
Ideally, a user should be able to create / modify subscription only if the referred entity / entity_type in the body is accessible to him.
Describe the solution you'd like
Rules for subscription creation and update should check that referred entities / entities types / paths ect, are authorised to the user. Most probably, it would be good to have a "specific" acl for that e.g. acl:subscribe (or something similar, using a custom namespace to avoid "breaking" the spec).
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
At the moment we use a static token, so the testing is not really end-to-end.
Describe the solution you'd like
Include keyclock in the docker compose and in the testing script.
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
Expose via an api who did what and when.
Describe the solution you'd like
TBD
Describe alternatives you've considered
Using a third party api gateway for the purpose
Additional context
N/A
Originally posted by @chicco785 in #17 (comment)
Is your feature request related to a problem? Please describe.
When a given resource is returned, public policies and requester's policies linked to that resource
should be linked in the header.
Describe the solution you'd like
See SOLID WAC spec.
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
support filter /policies
by resource, resourceType, agent, agentType
Describe the solution you'd like
This endpoint should be extended to support optional parameters for filtering by resource, resourceType, agent, agentType
https://github.com/orchestracities/anubis/blob/master/anubis-management-api/src/policies/routers.py#L103
Describe alternatives you've considered
N/A
Additional context
N/A
- Resource
- Resource Type
In case an JWT token is passed over, user id, roles and groups are used to
filter policies that are only valid for him. Unless the user is super admin or tenant admin.
To return policies from a service path tree, you can used the wildchar "#".
For example, using `/Path1/#` you will obtain policies for all subpaths,
such as: `/Path1/SubPath1` or `/Path1/SubPath1/SubSubPath1`.
"""
user_info = parse_auth_token(token)
owner = None
if user_info and user_info['is_super_admin']:
owner = None
elif user_info and user_info['tenants'] and fiware_service in user_info['tenants'] and "roles" in user_info['tenants'][fiware_service] and "tenant-admin" in user_info['tenants'][fiware_service]["roles"]:
owner = None
elif user_info and user_info['email']:
owner = user_info['email']
# we don't filter policies in case super admin or tenant admin
# TODO CHANGE LOGIC IT SHOULD LIST POLICIES I CONTROL
if agent_type and agent_type not in default.DEFAULT_AGENTS and agent_type not in default.DEFAULT_AGENT_TYPES:
raise HTTPException(
status_code=422,
}
@router.get("/me",
response_model=List[schemas.Policy],
responses=policies_not_json_responses,
summary="List policies for a given Tenant and Service Path that apply to me")
def my_policies(
token: str = Depends(auth_scheme),
fiware_service: Optional[str] = Header(
None),
fiware_servicepath: Optional[str] = Header(
'/#'),
mode: Optional[str] = None,
agent: Optional[str] = None,
accept: Optional[str] = Header(
'application/json'),
resource: Optional[str] = None,
resource_type: Optional[str] = None,
agent_type: Optional[str] = None,
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db)):
"""
Policies can be filtered by:
- Access Mode
- Agent
- Agent Type
- Resource
- Resource Type
In case an JWT token is passed over, user id, roles and groups are used to
filter policies that are only valid for him.
# TODO if no token, we should return policies for foaf:Agent!
To return policies from a service path tree, you can used the wildchar "#".
For example, using `/Path1/#` you will obtain policies for all subpaths,
such as: `/Path1/SubPath1` or `/Path1/SubPath1/SubSubPath1`.
"""
user_info = parse_auth_token(token)
if not user_info:
raise HTTPException(
status_code=403,
detail='missing access token, cannot identify user'
)
if agent_type and agent_type not in default.DEFAULT_AGENTS and agent_type not in default.DEFAULT_AGENT_TYPES:
raise HTTPException(
status_code=422,
detail='agent_type {} is not a valid agent type. Valid types are {} or {}'.format(
agent_type,
default.DEFAULT_AGENTS,
default.DEFAULT_AGENT_TYPES))
db_service_path = so.get_db_service_path(
db, fiware_service, fiware_servicepath)
db_service_path_id = list(map(so.compute_id, db_service_path))
db_policies = operations.get_policies_by_service_path(
db,
tenant=fiware_service,
service_path_id=db_service_path_id,
mode=mode,
agent=agent,
agent_type=agent_type,
resource=resource,
resource_type=resource_type,
skip=skip,
limit=limit,
user_info=user_info)
if accept == 'text/turtle':
return Response(
content=w_serialize(
db,
fiware_service,
fiware_servicepath,
db_policies),
media_type="text/turtle")
elif accept == 'text/rego':
return Response(
content=r_serialize(
db,
db_policies),
media_type="application/json")
else:
policies = []
for db_policy in db_policies:
policies.append(serialize_policy(db_policy))
return policies
@router.get("/",
response_model=List[schemas.Policy],
responses=policies_not_json_responses,
summary="List policies for a given Tenant and Service Path")
def read_policies(
token: str = Depends(auth_scheme),
fiware_service: Optional[str] = Header(
None),
fiware_servicepath: Optional[str] = Header(
Is your feature request related to a problem? Please describe.
I would like to measure the overhead introduced by anubis.
Describe the solution you'd like
Use a test tool such as vegeta to measure the processing overhead introduced by Anubis (in read and write operations)
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
The existing implementation should be revise against SOLID WAC specs.
Describe the solution you'd like
The concept was developed based on https://solid.github.io/web-access-control-spec/
but not fully aligned, should we align to this, what's missing? what are the steps needed?
For each element an issue should be opened.
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
Allow to query servicePaths in a tenant by 'name'
https://github.com/orchestracities/anubis/blob/master/anubis-management-api/src/tenants/routers.py#L128
Describe the solution you'd like
Add a new method based on
https://github.com/orchestracities/anubis/blob/master/anubis-management-api/src/tenants/operations.py#L63
that returns all the servicePath where the name match a given string.
Describe alternatives you've considered
N/A
Additional context
N/A
acl = Namespace("http://www.w3.org/ns/auth/acl#")
oc_acl = Namespace("http://voc.orchestracities.io/oc-acl#")
def parse_rdf_graph(data):
g = Graph()
g.bind("acl", acl)
g.bind("oc_acl", oc_acl)
g.parse(data=data)
nm = NamespaceManager(g)
policies = {}
for subj, pred, obj in g:
policy = str(subj).split("/")[-1]
if not policies.get(policy):
policies[policy] = {}
if pred == acl.agentClass:
policies[policy]["agentClass"] = redux(str(nm.normalizeUri(obj)))
if pred == acl.mode:
policies[policy]["mode"] = redux(str(nm.normalizeUri(obj)))
# TODO deal with this better.
if pred == acl.accessTo:
policies[policy]["accessTo"] = str(obj).split("/")[-1]
if pred == acl.default:
policies[policy]["accessTo"] = "default"
if pred == acl.accessToClass:
policies[policy]["accessToClass"] = str(
nm.normalizeUri(obj)).split(":")[-1]
return policies
def redux(uri: str):
if "http://www.w3.org/ns/auth/acl#" in uri:
return "acl:" + uri[len("http://www.w3.org/ns/auth/acl#") + 1:-1]
if "http://voc.orchestracities.io/oc-acl#" in uri:
return "oc-acl:" + \
uri[len("http://voc.orchestracities.io/oc-acl#") + 1:-1]
if "http://xmlns.com/foaf/0.1/" in uri:
return "foaf:" + uri[len("http://xmlns.com/foaf/0.1/") + 1:-1]
if "<" in uri[0] and ">" in uri[-1]:
return uri[1:-1]
return uri
def serialize(
db: Session,
fiware_service: [str],
Is your feature request related to a problem? Please describe.
On resource creation, it should be possible to assign an "owner" to the resource (which is the subject who created the resource), and he should have full control on the resource, i.e. automatically a policy with "acl:control" should be created for him. anyhow this should happen only when the api ack that the resource was created (so that in case of failure the policy is not created).
Describe the solution you'd like
A clear and concise description of what you want to happen.
Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
Additional context
Add any other context or screenshots about the feature request here.
Describe the bug
if you register a policy twice, no "already existing" is returned.
Is your feature request related to a problem? Please describe.
Currently, by implementing the WAC standard profile,
anubis supports only RBAC. While not formalized, the specs discuss the possibility to use ODRL to express obligations required to be met by agents prior to accessing a resource cf. authorization-extensions section in WAC spec
We could define the extension as
oc-acl:constraint a rdf:Property;
:comment "The information resource to which access is being granted.";
:domain acl:Authorization;
:label "Has Constraint"@en;
rdfs:range [
a owl:Class ;
owl:unionOf ( odrl:Constraint odrl:LogicalConstraint ) ;
] .
to allow acl rules to have constraints.
oc-acl:constraint [
odrl:leftOperand ? ;
odrl:operator odrl:isA ;
odrl:rightOperand ?
]
Describe the solution you'd like
A clear and concise description of what you want to happen.
Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
Additional context
Add any other context or screenshots about the feature request here.
Is your feature request related to a problem? Please describe.
In the past, in the context of quantumleap, we discussed about the opportunity to track this in the model:
orchestracities/ngsi-timeseries-api#18
Ideally, we can enforce it in the context of orion for resource creation. i.e. manipulated the json to insert something like owner: "user_id"
Something to think about.
Describe the solution you'd like
N/A
Describe alternatives you've considered
Do nothing, we can track resource ownership via ability to "control" resources in anubis and that's it.
Additional context
N/A
i was thinking to use anubi as name. he is the egyptian god of the underworld and its gatekeeper.
other ideas?
Describe the bug
generated wac have the following limitation:
@prefix acl: <http://www.w3.org/ns/auth/acl#> .
@prefix example: <http://example.org/> .
example:a0be6113-2339-40d7-9e85-56f93372f279 a acl:Authorization ;
acl:accessTo <http://example.org/*> ;
acl:agentClass <acl:AuthenticatedAgent> ;
acl:mode <acl:Write> .
when parsing acl, should we parse the resource identifier, rather than the path to access it?
allow_credentials - Indicate that cookies should be supported for cross-origin requests. Defaults to False. Also, allow_origins
cannot be set to ['*']
for credentials to be allowed, origins must be specified.
https://fastapi.tiangolo.com/tutorial/cors/#use-corsmiddleware
Originally posted by @chicco785 in #50 (comment)
also, we have to understand impact on cors of the usage of envoy.
Describe the bug
fiware_service
-> fiware-service
fiware_service_path
-> fiware-servicepath
Is your feature request related to a problem? Please describe.
Currently the api not tested.
Describe the solution you'd like
We should:
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
Currently policy data are pulled from the rego policies, this may create performance issues.
Describe the solution you'd like
When a policy is created or modified the auth api generates a payload to the opa data api. This data are then used to evaluate policies.
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
Current jwt validation does not include signature verification and other potential means
to provide stronger jwt validation.
Describe the solution you'd like
A more secure solution for jwt validation that include at least the signature verification of the token.
Potentially also issuer and aud.
See https://www.openpolicyagent.org/docs/latest/oauth-oidc/#metadata-discovery
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
Today we have a rule package only for ngsi-v2. We should have one also for NGSI-LD
Describe the solution you'd like
Create a new rego package for NGSI-LD apis.
Describe alternatives you've considered
N/A
Additional context
N/A
to be defined
from ..tenants import models as tm
# TODO it would be good to have also the list of owners, but query needs
# to be defined
def get_resources(
db: Session,
tenant: str = None,
import express from "express"
import bp from "body-parser"
import { createLibp2p } from 'libp2p'
import { TCP } from '@libp2p/tcp'
import { WebSockets } from '@libp2p/websockets'
import { Mplex } from '@libp2p/mplex'
import { Noise } from '@chainsafe/libp2p-noise'
import { CID } from 'multiformats/cid'
import { KadDHT } from '@libp2p/kad-dht'
import all from 'it-all'
import delay from 'delay'
import { Bootstrap } from '@libp2p/bootstrap'
import * as json from 'multiformats/codecs/json'
import { sha256 } from 'multiformats/hashes/sha2'
import { FloodSub } from '@libp2p/floodsub'
import { fromString as uint8ArrayFromString } from 'uint8arrays/from-string'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import { MulticastDNS } from '@libp2p/mdns'
import axios from 'axios'
import fs from 'fs'
import { Multiaddr } from "@multiformats/multiaddr";
import dns from "dns/promises";
import cors from 'cors';
// Configuration for the port used by this node
const server_port = process.env.SERVER_PORT || 8099
// Uri of the Anubis API connected to this middleware
const anubis_api_uri = process.env.ANUBIS_API_URI || "127.0.0.1:8085"
// The multiaddress format address this middleware listens on
const listen_address = process.env.LISTEN_ADDRESS || '/dnsaddr/localhost/tcp/49662'
// Is this a private organisation? (Private org won't share policies that aren't of a specific user only)
const is_private_org = process.env.IS_PRIVATE_ORG || "true"
// Convert DNS address to IP address (solves Docker issues)
var listen_ma = new Multiaddr(listen_address)
var options = listen_ma.toOptions()
if(listen_address.includes("dnsaddr") && options.host != 'localhost') {
const lookup = await dns.lookup(options.host)
listen_ma = new Multiaddr(listen_ma.toString().replace(options.host, lookup.address).replace("dnsaddr", "ip4"))
}
// Setting up Node app
var app = express()
app.use(cors())
app.use(bp.json())
app.use(bp.urlencoded({ extended: true }))
// Keeping track of resources being provided
var providedResources = []
// Setting up the Libp2p node
const node = await createLibp2p({
addresses: {
listen: [listen_ma]
},
transports: [new TCP(), new WebSockets()],
streamMuxers: [new Mplex()],
connectionEncryption: [new Noise()],
dht: new KadDHT(),
peerDiscovery: [
new MulticastDNS({
interval: 20e3
})
],
connectionManager: {
autoDial: true
},
pubsub: new FloodSub(),
relay: {
enabled: true,
hop: {
enabled: true
},
advertise: {
enabled: true,
}
}
})
// Endpoint to retrieve node metadata
app.get('/metadata', async(req, res) => {
res.json({"policy_api_uri": anubis_api_uri})
})
// Endpoint for receiving resources from the mobile app
app.post('/resource/mobile/retrieve', async(req, res) => {
if (!Object.keys(req.body).length) {
return res.status(400).json({
message: "Request body cannot be empty",
})
}
var { resource, service, servicepath } = req.body
if (!resource) {
res.status(400).json({
message: "Ensure you sent a resource field",
})
}
if (!service) {
res.status(400).json({
message: "Ensure you sent a service field",
})
}
if (!servicepath) {
res.status(400).json({
message: "Ensure you sent a servicepath field",
})
}
// TODO: Email of the user
var responseData = [
{
"usrMail": "[email protected]",
"usrData": [
{
"id": resource,
"description": "test",
"children": []
}
]
}
]
const bytes = json.encode({ resource: resource })
const hash = await sha256.digest(bytes)
const cid = CID.create(1, json.code, hash)
var providers = []
try {
providers = await all(node.contentRouting.findProviders(cid, { timeout: 3000 }))
}
catch(error) {
res.end(`No providers for ${resource}`)
return
}
for (const provider of providers) {
var providerPolicyApi = null
await axios({
method: 'get',
url: `http://${provider.multiaddrs[0].nodeAddress().address}:8098/metadata`
})
.then(async function (response) {
providerPolicyApi = response.data["policy_api_uri"]
})
.catch(function (error) {
console.log(`Can't retrieve policy API URL for provider ${provider.multiaddrs[0].nodeAddress().address}`)
})
if(!providerPolicyApi) {
continue
}
await axios({
method: 'get',
url: `http://${providerPolicyApi}/v1/policies`,
headers: {
'fiware-Service': service,
'fiware-Servicepath': servicepath
},
params: {
'resource': resource
}
})
.then(async function (response) {
for (const policy_entry of response.data) {
if(is_private_org != "true") {
var filtered_agents = policy_entry.agent.filter(a => !a.includes("acl:agent:"))
if(filtered_agents.length > 0) {
continue
}
}
responseData[0].usrData[0].children.push({"id": policy_entry["id"], "actorType": policy_entry["agent"], "mode": policy_entry["mode"]})
}
})
.catch(function (error) {
console.log(error.response.data)
})
}
res.json({responseData})
})
// Endpoint for providing a resource from the mobile app
app.post('/resource/mobile/send', async(req, res) => {
if (!Object.keys(req.body).length) {
return res.status(400).json({
message: "Request body cannot be empty",
})
}
var { policies, service, servicepath } = req.body
if (!policies) {
res.status(400).json({
message: "Ensure you sent a policies field",
})
}
if (!service) {
res.status(400).json({
message: "Ensure you sent a service field",
})
}
if (!servicepath) {
res.status(400).json({
message: "Ensure you sent a servicepath field",
})
}
for(const entry of policies) {
const usrMail = entry.usrMail
for(const resource of entry.usrData) {
const resId = resource.id
for(const policy of resource.children) {
var modes = []
for(const m of policy.mode.split(",")) {
modes.push(m)
}
modes = modes.filter(e => e != '')
var new_policy = {
"id": policy.id,
"access_to": resId,
"resource_type": "mobile",
"mode": modes,
"agent": [
policy.actorType
]
}
var message = {
"action": "send_mobile",
"policy": new_policy,
"service": service,
"servicepath": servicepath,
}
message = JSON.stringify(message)
await node.pubsub.publish(resId, uint8ArrayFromString(message)).catch(err => {
console.error(err)
res.end(`Error: ${err}`)
})
}
}
}
res.json({})
})
// Endpoint for providing a resource
app.post('/resource/provide', async(req, res) => {
if (!Object.keys(req.body).length) {
return res.status(400).json({
message: "Request body cannot be empty",
})
}
var { resource } = req.body
if (!resource) {
res.status(400).json({
message: "Ensure you sent a resource field",
})
}
const bytes = json.encode({ resource: resource })
const hash = await sha256.digest(bytes)
const cid = CID.create(1, json.code, hash)
await node.contentRouting.provide(cid)
providedResources.push(resource)
console.log(`Provided policy for resource ${resource}`)
res.end(`Provided policy for resource ${resource}`)
})
// Endpoint for subscribing to a resource topic
app.post('/resource/subscribe', async(req, res) => {
if (!Object.keys(req.body).length) {
return res.status(400).json({
message: "Request body cannot be empty",
})
}
var { resource, policy, service, servicepath } = req.body
if (!resource) {
res.status(400).json({
message: "Ensure you sent a resource field",
})
}
if (!service) {
res.status(400).json({
message: "Ensure you sent a service field",
})
}
if (!servicepath) {
res.status(400).json({
message: "Ensure you sent a servicepath field",
})
}
const topics = await node.pubsub.getTopics()
if (!topics.includes(resource)) {
await node.pubsub.subscribe(resource)
console.log(`Subscribed to ${resource}`)
}
const bytes = json.encode({ resource: resource })
const hash = await sha256.digest(bytes)
const cid = CID.create(1, json.code, hash)
var providers = []
try {
providers = await all(node.contentRouting.findProviders(cid, { timeout: 3000 }))
}
catch(error) {
res.end(`Subscribed to ${resource}, no other providers found`)
return
}
console.log(`Syncing with other providers for ${resource}...`)
for (const provider of providers) {
var providerPolicyApi = null
await axios({
method: 'get',
url: `http://${provider.multiaddrs[0].nodeAddress().address}:8098/metadata`
})
.then(async function (response) {
providerPolicyApi = response.data["policy_api_uri"]
})
.catch(function (error) {
console.log(`Can't retrieve policy API URL for provider ${provider.multiaddrs[0].nodeAddress().address}`)
})
if(!providerPolicyApi) {
continue
}
await axios({
method: 'post',
url: `http://${anubis_api_uri}/v1/tenants/`,
data: {"name": service}
})
.then(async function (response) {
console.log(`Created Tenant ${service}`)
})
.catch(function (error) {
console.log(`No new Tenant created`)
})
await axios({
method: 'get',
url: `http://${providerPolicyApi}/v1/policies`,
headers: {
'fiware-Service': service,
'fiware-Servicepath': servicepath
},
params: {
'resource': resource
}
})
.then(async function (response) {
for (const policy_entry of response.data) {
// TODO: Concurrency
if(is_private_org != "true") {
var filtered_agents = policy_entry.agent.filter(a => !a.includes("acl:agent:"))
if(filtered_agents.length > 0) {
continue
}
}
await axios({
method: 'post',
url: `http://${anubis_api_uri}/v1/policies`,
headers: {
'fiware-Service': service,
'fiware-Servicepath': servicepath
},
data: policy_entry
})
.then(function (r) {
console.log(r.data)
})
.catch(function (err) {
console.log(err.response.data)
})
}
})
.catch(function (error) {
console.log(error.response.data)
})
}
res.end(`Subscribed to ${resource}`)
})
// Endpoint when a new policy is created
app.post('/resource/policy/new', async(req, res) => {
if (!Object.keys(req.body).length) {
return res.status(400).json({
message: "Request body cannot be empty",
})
}
var { resource, policy, service, servicepath } = req.body
if (!resource) {
res.status(400).json({
message: "Ensure you sent a resource field",
})
}
if (!policy) {
res.status(400).json({
message: "Ensure you sent a policy field",
})
}
if (!service) {
res.status(400).json({
message: "Ensure you sent a service field",
})
}
if (!servicepath) {
res.status(400).json({
message: "Ensure you sent a servicepath field",
})
}
var message = {
"action": "post",
"policy_id": policy,
"service": service,
"servicepath": servicepath,
}
message = JSON.stringify(message)
await node.pubsub.publish(resource, uint8ArrayFromString(message)).catch(err => {
console.error(err)
res.end(`Error: ${err}`)
})
res.end("Policy message sent: " + message)
console.log("Policy message sent: " + message)
})
// Endpoint when a policy is updated
app.post('/resource/policy/update', async(req, res) => {
if (!Object.keys(req.body).length) {
return res.status(400).json({
message: "Request body cannot be empty",
})
}
var { resource, policy, service, servicepath } = req.body
if (!resource) {
res.status(400).json({
message: "Ensure you sent a resource field",
})
}
if (!policy) {
res.status(400).json({
message: "Ensure you sent a policy field",
})
}
if (!service) {
res.status(400).json({
message: "Ensure you sent a service field",
})
}
if (!servicepath) {
res.status(400).json({
message: "Ensure you sent a servicepath field",
})
}
var message = {
"action": "put",
"policy_id": policy,
"service": service,
"servicepath": servicepath,
}
message = JSON.stringify(message)
await node.pubsub.publish(resource, uint8ArrayFromString(message)).catch(err => {
console.error(err)
res.end(`Error: ${err}`)
})
res.end("Policy message sent: " + message)
console.log("Policy message sent: " + message)
})
// Endpoint when a policy is deleted
app.post('/resource/policy/delete', async(req, res) => {
if (!Object.keys(req.body).length) {
return res.status(400).json({
message: "Request body cannot be empty",
})
}
var { resource, policy, service, servicepath } = req.body
if (!resource) {
res.status(400).json({
message: "Ensure you sent a resource field",
})
}
if (!policy) {
res.status(400).json({
message: "Ensure you sent a policy field",
})
}
if (!service) {
res.status(400).json({
message: "Ensure you sent a service field",
})
}
if (!servicepath) {
res.status(400).json({
message: "Ensure you sent a servicepath field",
})
}
var message = {
"action": "delete",
"policy_id": policy,
"service": service,
"servicepath": servicepath,
}
message = JSON.stringify(message)
await node.pubsub.publish(resource, uint8ArrayFromString(message)).catch(err => {
console.error(err)
res.end(`Error: ${err}`)
})
res.end("Policy message sent: " + message)
console.log("Policy message sent: " + message)
})
// Function to process a message arriving on a topic (resource)
async function processTopicMessage(evt) {
const sender = await node.peerStore.addressBook.get(evt.detail.from)
const message = JSON.parse(uint8ArrayToString(evt.detail.data))
console.log(`Node received: ${uint8ArrayToString(evt.detail.data)} on topic ${evt.detail.topic}`)
if(message.action == "send_mobile") {
await axios({
method: 'post',
url: `http://${anubis_api_uri}/v1/tenants/`,
data: {"name": message.service}
})
.then(async function (response) {
console.log(`Created Tenant ${message.service}`)
})
.catch(function (error) {
console.log(error)
})
await axios({
method: 'post',
url: `http://${anubis_api_uri}/v1/policies`,
headers: {
'fiware-Service': message.service,
'fiware-Servicepath': message.servicepath
},
data: message.policy
})
.then(function (r) {
console.log(r)
})
.catch(function (err) {
console.log(err.response.data)
})
return
}
var providerPolicyApi = null
await axios({
method: 'get',
url: `http://${sender[0].multiaddr.nodeAddress().address}:8098/metadata`
})
.then(async function (response) {
providerPolicyApi = response.data["policy_api_uri"]
})
.catch(function (error) {
console.log(error)
})
if(message.action == "delete") {
await axios({
method: 'delete',
url: `http://${anubis_api_uri}/v1/policies/${message.policy_id}`,
headers: {
'fiware-Service': message.service,
'fiware-Servicepath': message.servicepath
},
data: response.data
})
.then(function (r) {
console.log(r)
})
.catch(function (err) {
console.log(err)
})
return
}
await axios({
method: 'get',
url: `http://${providerPolicyApi}/v1/policies/${message.policy_id}`,
headers: {
'fiware-Service': message.service,
'fiware-Servicepath': message.servicepath
}
})
.then(async function (response) {
if(message.action == "post") {
await axios({
method: 'post',
url: `http://${anubis_api_uri}/v1/tenants/`,
data: {"name": message.service}
})
.then(async function (response) {
console.log(`Created Tenant ${resource}`)
})
.catch(function (error) {
console.log(error)
})
await axios({
method: 'post',
url: `http://${anubis_api_uri}/v1/policies`,
headers: {
'fiware-Service': message.service,
'fiware-Servicepath': message.servicepath
},
data: response.data
})
.then(function (r) {
console.log(r)
})
.catch(function (err) {
console.log(err.response.data)
})
}
else if(message.action == "put") {
await axios({
method: 'put',
url: `http://${anubis_api_uri}/v1/policies/${message.policy_id}`,
headers: {
'fiware-Service': message.service,
'fiware-Servicepath': message.servicepath
},
data: response.data
})
.then(function (r) {
console.log(r)
})
.catch(function (err) {
console.log(err)
})
}
})
.catch(function (error) {
console.log(error)
})
}
// Saving config to file
async function saveConfiguration() {
var persistentDataFileStream = fs.createWriteStream('data.json')
const topics = await node.pubsub.getTopics()
let data2 = JSON.stringify({"topics": topics, "resources": providedResources})
persistentDataFileStream.write(data2)
persistentDataFileStream.close()
}
// Starting server
var server = app.listen(server_port, async() => {
await node.start()
try {
let rawdata = fs.readFileSync('data.json')
let data = JSON.parse(rawdata)
for(const resource of data.resources) {
providedResources.push(resource)
const bytes = json.encode({ resource: resource })
const hash = await sha256.digest(bytes)
const cid = CID.create(1, json.code, hash)
try {
await node.contentRouting.provide(cid)
}
catch(err) {
console.log(`Failed to initially provide ${resource}`)
}
}
for(const topic of data.topics) {
node.pubsub.subscribe(topic)
}
await saveConfiguration()
}
catch(err) {
console.log("Couldn't read any initial config")
}
console.log("Node started with:")
node.getMultiaddrs().forEach((ma) => console.log(`${ma.toString()}`))
node.connectionManager.addEventListener('peer:connect', (evt) => {
const connection = evt.detail
console.log('Connection established to:', connection.remotePeer.toString())
})
node.addEventListener('peer:discovery', async(evt) => {
const peer = evt.detail
if (node.peerId.toString() == peer.id.toString()) {
return
}
var peerId = node.peerStore.addressBook.get(peer.id)
if (!peerId) {
console.log('Discovered:', peer.id.toString())
node.peerStore.addressBook.set(peer.id, peer.multiaddrs)
node.dial(peer.id)
}
})
await node.pubsub.addEventListener("message", (evt) => processTopicMessage(evt))
await delay(1000)
console.log(node.peerId.toString())
var host = server.address().address
var port = server.address().port
console.log("App listening at http://%s:%s", host, port)
})
Describe the bug
when serialized / parsed to turtle an agent should be an entity of type foaf:Agent with foaf:mbox to define the email.
To Reproduce
tenant:policy5 a acl:Authorization ;
acl:agentClass <acl:agent:[email protected]> ;
acl:accessTo entity:test ;
acl:accessToClass oc-acl:entity ;
acl:mode acl:Read .
Expected behavior
tenant:policy5 a acl:Authorization ;
acl:agentClass acl:agent:x ;
acl:accessTo entity:test ;
acl:accessToClass oc-acl:entity ;
acl:mode acl:Read .
acl:agent:x a foaf:Agent
foaf:mbox "[email protected]"
resource_type: str = None,
skip: int = 0,
limit: int = 100,
user_info: dict = None,
owner: str = None):
# TODO: filter policy that owner (email based) controls
if mode is not None and agent is not None:
db_policies = db.query(
models.Policy).join(
Is your feature request related to a problem? Please describe.
Implement /version
endpoint
Is your feature request related to a problem? Please describe.
Today we don't support loading db connection configuration from env variables.
Describe the solution you'd like
Load db configuraiton from env variable.
Describe alternatives you've considered
N/A
Additional context
N/A
To return policies from a service path tree, you can used the wildchar "#".
For example, using /Path1/#
you will obtain policies for all subpaths,
such as: /Path1/SubPath1
or /Path1/SubPath1/SubSubPath1
.
}
@router.get("/me",
response_model=List[schemas.Policy],
responses=policies_not_json_responses,
summary="List policies for a given Tenant and Service Path that apply to me")
def my_policies(
token: str = Depends(auth_scheme),
fiware_service: Optional[str] = Header(
None),
fiware_servicepath: Optional[str] = Header(
'/#'),
mode: Optional[str] = None,
agent: Optional[str] = None,
accept: Optional[str] = Header(
'application/json'),
resource: Optional[str] = None,
resource_type: Optional[str] = None,
agent_type: Optional[str] = None,
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db)):
"""
Policies can be filtered by:
- Access Mode
- Agent
- Agent Type
- Resource
- Resource Type
In case an JWT token is passed over, user id, roles and groups are used to
filter policies that are only valid for him.
# TODO if no token, we should return policies for foaf:Agent!
To return policies from a service path tree, you can used the wildchar "#".
For example, using `/Path1/#` you will obtain policies for all subpaths,
such as: `/Path1/SubPath1` or `/Path1/SubPath1/SubSubPath1`.
"""
user_info = parse_auth_token(token)
if not user_info:
raise HTTPException(
status_code=403,
detail='missing access token, cannot identify user'
)
if agent_type and agent_type not in default.DEFAULT_AGENTS and agent_type not in default.DEFAULT_AGENT_TYPES:
raise HTTPException(
status_code=422,
detail='agent_type {} is not a valid agent type. Valid types are {} or {}'.format(
agent_type,
default.DEFAULT_AGENTS,
default.DEFAULT_AGENT_TYPES))
db_service_path = so.get_db_service_path(
db, fiware_service, fiware_servicepath)
db_service_path_id = list(map(so.compute_id, db_service_path))
db_policies = operations.get_policies_by_service_path(
db,
tenant=fiware_service,
service_path_id=db_service_path_id,
mode=mode,
agent=agent,
agent_type=agent_type,
resource=resource,
resource_type=resource_type,
skip=skip,
limit=limit,
user_info=user_info)
if accept == 'text/turtle':
return Response(
content=w_serialize(
db,
fiware_service,
fiware_servicepath,
db_policies),
media_type="text/turtle")
elif accept == 'text/rego':
return Response(
content=r_serialize(
db,
db_policies),
media_type="application/json")
else:
policies = []
for db_policy in db_policies:
policies.append(serialize_policy(db_policy))
return policies
@router.get("/",
response_model=List[schemas.Policy],
responses=policies_not_json_responses,
summary="List policies for a given Tenant and Service Path")
def read_policies(
token: str = Depends(auth_scheme),
fiware_service: Optional[str] = Header(
None),
fiware_servicepath: Optional[str] = Header(
Is your feature request related to a problem? Please describe.
Design a decentralised middleware supporting the continuous governance of the policies and their synchronisation across different platforms leveraging a gossip-like protocol or alternative message exchange mechanisms.
Describe the solution you'd like
A document that guides the implementation of the middleware for policy sharing.
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
Current tests covers only tenants and service paths.
Describe the solution you'd like
Tests should:
a
, when I do a get on the created tenant, does the body contain the value a
for the name attribute?Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
Additional context
Add any other context or screenshots about the feature request here.
we should no use the path right?
Is your feature request related to a problem? Please describe.
this allows to share policies externally
Describe the solution you'd like
N/A
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
The open api specs should be automatically exported so that developers can look into it
and that it can be referenced from the documentation.
Describe the solution you'd like
Ideally, after each new pr merge, the open api file is generated, stored locally, and pushed to an online ui,
e.g. hub swagger. When a new release is made, the numbering of the api version is automatically updated.
This could be of help: https://pypi.org/project/fastapi-export-openapi/
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
Ideally when a new resource is created a default policy should be applied to it.
Describe the solution you'd like
This is just an idea, feedbacks are welcome:
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
NGSI apis also include subscriptions, we should test and validate acl also for subscriptions.
Describe the solution you'd like
Having a set of rules for subscription resource type.
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
Currently we support serialization of internal policies as turtle, but we don't support the import of wac policies serialised as turtle or rdf.
Describe the solution you'd like
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
Current rules can be split in two parts:
1 - Rule template
2 - Data to fill in the rule template
This may simplify a lot the rule generation, and basically what will need to be pushed to the opa is just the data part of the rule, while the rule template can be static and customised to the different APIs.
Describe the solution you'd like
Some thing that implements this design:
Rule template:
# Allow the action if the user is granted permission to perform the action.
allow {
# Find permissions for the user.
some permission
user_is_granted[permission]
# Check if the permission permits the action.
input.action == permission.action
input.type == permission.type
}
Rule data:
{
"users": {
"alice": {
"roles": [
"admin"
]
},
"bob": {
"roles": [
"employee",
"billing"
]
},
"eve": {
"roles": [
"customer"
],
"location": {
"country": "US",
"ip": "8.8.8.8"
}
}
},
"role_permissions": {
"customer": [
{
"action": "read",
"type": "dog"
},
{
"action": "read",
"type": "cat"
},
{
"action": "adopt",
"type": "dog"
},
{
"action": "adopt",
"type": "cat"
}
],
"employee": [
{
"action": "read",
"type": "dog"
},
{
"action": "read",
"type": "cat"
},
{
"action": "update",
"type": "dog"
},
{
"action": "update",
"type": "cat"
}
],
"billing": [
{
"action": "read",
"type": "finance"
},
{
"action": "update",
"type": "finance"
}
]
}
}
Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
Additional context
Add any other context or screenshots about the feature request here.
Is your feature request related to a problem? Please describe.
The current readme needs to be expanded to a fully fledged documentation.
Describe the solution you'd like
Create https://anubis.readthedocs.io/en/latest/
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
Support the decentralized governance of policies, so that when resource 'A' is queried and imported from system '1' to system '2', the policies linked to 'A' are transferred as well. This also implies that if user 'X' owning resource 'A' update policies related to 'A', policies are propagated.
Describe the solution you'd like
To be detailed based on #26
Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.
Additional context
Add any other context or screenshots about the feature request here.
Is your feature request related to a problem? Please describe.
APIs like orion have base end points for resource type that returns the whole content. Either these are blocked, unless a user has access to all entities, or content should be automatically filtered.
Describe the solution you'd like
Option 1. only who can list all resources of type entities and all resources of type entity type can access /entities and /types
Option 2. we compute the list of resources a user can access (is that possible with opa?) and manipulate request query so that data are correctly filtered based on that.
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
Ideally a resource owner should be able to attach the policies he wants for his resource directly at creation time.
Describe the solution you'd like
A mechanism that allow to attach policy definition in the header, parse them and inject them in auth api, following the resource creation.
Describe alternatives you've considered
N/A
Additional context
N/A
Is your feature request related to a problem? Please describe.
At the time being we haven't thought about acl for policies management.
Describe the solution you'd like
Enable acl for policy management leveraging our own stack.
Describe alternatives you've considered
N/A
Additional context
N/A
anubis/config/opa-service/policy.rego
Line 11 in f867379
from the WAC spec
NOTE: HTTP Method and Access Mode Mapping
When the target of the HTTP request is the ACL resource, the operation can only be allowed with the acl:Control access mode.Having acl:Control does not imply that the agent has acl:Read or acl:Write access to the resource itself, just to its corresponding ACL resource. For example, an agent with control access can disable their own write access (to prevent accidental over-writing of a resource by an application), but be able to change their access levels at a later point (since they retain acl:Control access).
The HTTP GET method request targeting a resource can only be allowed with the acl:Read access mode.
The HTTP POST can be used to create a new resource in a container or add information to existing resources (but not remove resources or its contents) with either acl:Append or acl:Write.
As the HTTP PUT method requests to create or replace the resource state, the acl:Write access mode would be required.
As the processing of HTTP PATCH method requests depends on the request semantics and content, acl:Append can allow requests using SPARQL 1.1 Update’s [SPARQL11-UPDATE] INSERT DATA operation but not DELETE DATA, whereas acl:Write would allow both operations.
As the HTTP DELETE method requests to remove a resource, the acl:Write access mode would be required.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.