Coder Social home page Coder Social logo

orchestracities / anubis Goto Github PK

View Code? Open in Web Editor NEW
7.0 3.0 5.0 1.15 MB

Anubis: a flexible policy enforcement solution for NGSI APIs (and beyond!)

Home Page: https://anubis-pep.readthedocs.org

License: Apache License 2.0

Shell 11.47% Python 44.17% Open Policy Agent 12.29% Lua 22.58% Dockerfile 0.31% JavaScript 9.18%
ngsi ngsi-v2 ngsiv2 opa acl rego access-control wac

anubis's People

Contributors

cerfoglg avatar chicco785 avatar dependabot[bot] avatar grigri71 avatar jason-fox avatar valecant avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

anubis's Issues

Email of the user

// TODO: Email of the user

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)
})

Document pypi package installation / location

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

Introduce support for acl:origin

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

advanced support for resource entity and entitytype check in subscriptions

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

extend demo / test with keyclock

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

access log

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

notify policy with data

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

support filter `/policies` by resource, resourceType, agent, agentType

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

read policy when a token is passed should return policies controlled by the user

# TODO CHANGE LOGIC IT SHOULD LIST POLICIES I CONTROL

      - 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,

if no token, we should return policies for foaf:Agent!

# TODO if no token, we should return policies for foaf:Agent!

}


@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(

measure performance with and without Anubis PEP

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

revise against solid spec

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

support filter `/{tenant_id}/service_paths` by servicePath name

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

check if there is a better way to extract resource linked by accessTo

# TODO deal with this better.

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],

Resource owner

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.

attribute based access control

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.

Think about resource ownership tracking for NGSI / NGSI-LD

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

new project name

i was thinking to use anubi as name. he is the egyptian god of the underworld and its gatekeeper.
other ideas?

fix wac serialization

Describe the bug
generated wac have the following limitation:

  1. prefix namespace is not generated, while it should be. it would be good for example that the prefix is generated from the tenant name.
  2. the base path to the resource is hard coded, while it should be configurable (and eventually even per tenant & resourceType)
        @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> .

add test for auth-management-api

Is your feature request related to a problem? Please describe.

Currently the api not tested.

Describe the solution you'd like
We should:

  • define test cases
  • develop ci for the test cases to be verified

Describe alternatives you've considered
N/A

Additional context
N/A

move from pull to push model for policy data

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

improve jwt validation to include signature

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

support ngsi-ld api

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

check potential concurrency issues when retrieving policies on subscription

// TODO: Concurrency

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)
})

correct support for agents

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]"

support database configuration

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

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.

# TODO if no token, we should return policies for foaf:Agent!

}


@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(

design policy sharing middleware

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

expand api tests

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:

  • cover policies (including translation check)
  • validate body also for tenant / service api, e.g.:
    • if i a create a tenant named 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.

change user identifier to email

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

automatically export open api specs

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

Default policies

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:

  • we should be able to tag a set of policies within a tenant and for a given resource type as default policy.
  • when a resource without policy is requested, this policies should be applied.

Describe alternatives you've considered
N/A

Additional context
N/A

support for subscriptions in rego

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

rdf/turtle wac parser

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

  1. The wac policy is parsed (turtle or rdf)
  2. The wac policy is validated
  3. The wac policy is stored in the api

Describe alternatives you've considered

N/A

Additional context

N/A

Split rules from "their" data

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.

decentralised middleware for policy governance

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.

dealing with "listing" apis

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

allow creation of policies at resource creation time

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

acl for policies management

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

update mappings and related documentation

scope_method := {"acl:Read": ["GET"], "acl:Write": ["POST"], "acl:Control": ["PUT", "DELETE"]}

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.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.