Skip to content
This repository has been archived by the owner on Jan 13, 2019. It is now read-only.

Commit

Permalink
Implemented a role based IAM model.
Browse files Browse the repository at this point in the history
API endpoints are now secured with role based ACLs. Also implemented client
approval flow.

Added a role based permission model for IAM.

BUG=

Review URL: https://codereview.appspot.com/326780043.
  • Loading branch information
scudette committed Jun 12, 2017
1 parent 56372e6 commit 9e51d9f
Show file tree
Hide file tree
Showing 26 changed files with 831 additions and 348 deletions.
41 changes: 40 additions & 1 deletion applications/Rekall/controllers/clients.py
@@ -1,7 +1,46 @@
# Controller to manage clients.
import json
import api
import utils

from gluon.globals import current


def index():
form = FORM('Search:', INPUT(_name='q'), INPUT(_type='submit'))
form.accepts(request, session)
return dict(form=form)


def request_approval():
"""Request an approval to view client."""
client_id = request.vars.client_id
if client_id:
roles = ["Examiner", "Investigator"]
approvers = api.api_dispatcher.call(
current, "client.approver.list", client_id).get("data")
inputs = [SELECT(*roles,
_name="role",
requires=IS_IN_SET(roles)),
SELECT(*sorted(approvers),
_name="approver",
requires=IS_IN_SET(approvers)),
]

form = utils.build_form(inputs)
if form.accepts(request, session):
# Request an approval from the API
api.api_dispatcher.call(
current, "client.approver.request", client_id,
form.vars.approver, form.vars.role)
#redirect(URL(c="default", f="index"))

return dict(form=form, client_id=client_id)


def approve_request():
"""Approves a request from another user."""
client_id = request.vars.client_id
role = request.vars.role
user = request.vars.user
if client_id and role and user:
return dict(client_id=client_id, user=user, role=role)
80 changes: 31 additions & 49 deletions applications/Rekall/controllers/flows.py
@@ -1,19 +1,20 @@
# Launch flows.
import json
import re
import os
import time
import uuid

from gluon.globals import current
from gluon import http
from gluon import validators

from google.appengine.ext import blobstore

from rekall_lib.types import agent

import api
import utils


from api import plugins


Expand Down Expand Up @@ -172,6 +173,9 @@ def Build(self):
return inputs

def ParseValue(self, value, desc):
if value is None:
return value

type = desc['type']
if type == 'Boolean':
state = value == "on"
Expand All @@ -187,7 +191,7 @@ def ParseValue(self, value, desc):
if type in ("ArrayStringParser", "ArrayIntParser") and not value:
return

if type == "ChoiceArray" and set(value) == set(desc.get("default", [])):
if type == "ChoiceArray" and set(value) == set(desc.get("default") or []):
return

return value
Expand Down Expand Up @@ -237,6 +241,16 @@ def Build(self):

return inputs

def ParseForm(self, vars):
result = {}
for desc in self.api_info:
name = desc["name"]
value = self.ParseValue(vars.get("session_" + name), desc)
if value is not None:
result[name] = value

return result


def launch():
plugin = request.vars.plugin
Expand All @@ -259,57 +273,25 @@ def launch():
view_args = dict(plugin=plugin, client_id=client_id,
inputs=inputs, api_info=api_info,
launched=False, plugin_arg=None,
error=None,
session_inputs=session_inputs,
form=form)

if form.accepts(request, session):
response.flash = 'form accepted'
plugin_arg = builder.ParseForm(form.vars)
# Schedule the flow for the client.
collection_id = unicode(uuid.uuid4())
flow_id = unicode(uuid.uuid4())
flow = agent.Flow.from_keywords(
flow_id=flow_id,
created_time=time.time(),
rekall_session=dict(live="API"),
file_upload=dict(
__type__="FileUploadLocation",
flow_id=flow_id,
base=URL(c="control", f='file_upload', host=True)),
ticket=dict(
location=dict(
__type__="HTTPLocation",
base=utils.route_api('/control/ticket'),
path_prefix=flow_id,
)),
actions=[
dict(__type__="PluginAction",
plugin=plugin,
args=builder.ParseForm(form.vars),
collection=dict(
__type__="JSONCollection",
id=collection_id,
location=dict(
__type__="BlobUploader",
base=URL(
c="control", f='upload', host=True),
path_template=(
"collection/%s/{part}" % collection_id),
))
)])

db.flows.insert(
flow_id=flow_id,
client_id=client_id,
flow=flow.to_primitive(),
)

db.collections.insert(
collection_id=collection_id,
flow_id=flow_id)

view_args["launched"] = True
view_args["plugin_arg"] = plugin_arg
rekall_session = session_builder.ParseForm(form.vars)

# The actual flow scheduling is done via the API.
try:
api.api_dispatcher.call(
current, "flows.plugins.launch",
client_id, rekall_session, plugin, plugin_arg)
except http.HTTP as e:
# Permission denied errors require approval.
view_args["error"] = e

return http.redirect(
URL(f="inspect_list", vars=dict(client_id=client_id)))

return dict(**view_args)

Expand Down
20 changes: 2 additions & 18 deletions applications/Rekall/controllers/users.py
@@ -1,7 +1,7 @@
# User and account management.
from gluon.globals import current
import api

import utils

def manage():
return dict()
Expand All @@ -10,32 +10,16 @@ def manage():
def add():
"""Add a new user role."""
inputs = [INPUT(_name="user",
_class="form-control",
_title="User Email"),
INPUT(_name="resource",
_class="form-control",
_title="Resource",
value="/"),
SELECT(*sorted(users.roles),
_name="role",
_class="form-control",
requires=IS_IN_SET(users.roles)),
]

elements = []
for input in inputs:
name = input.attributes["_name"]

elements.append(DIV(
LABEL(name,
_class="col-sm-2 control-label",
_for=name),
DIV(input, _class="col-sm-7"),
_class="form-group"))

elements.append(INPUT(_type="submit"))

form = FORM(*elements, _class="form-horizontal")
form = utils.build_form(inputs)
if form.accepts(request, session):
api.api_dispatcher.call(current, "users.add")

Expand Down
29 changes: 0 additions & 29 deletions applications/Rekall/models/db.py
Expand Up @@ -84,35 +84,6 @@
# (more options discussed in gluon/tools.py)
# -------------------------------------------------------------------------

from gluon.tools import Auth, Service, PluginManager

# host names must be a list of allowed host names (glob syntax allowed)
auth = Auth(db, host_names=myconf.get('host.names'))
service = Service()
plugins = PluginManager()

# -------------------------------------------------------------------------
# create all tables needed by auth if not custom tables
# -------------------------------------------------------------------------
auth.define_tables(username=False, signature=False)

# -------------------------------------------------------------------------
# configure email
# -------------------------------------------------------------------------
mail = auth.settings.mailer
mail.settings.server = 'logging' if request.is_local else myconf.get('smtp.server')
mail.settings.sender = myconf.get('smtp.sender')
mail.settings.login = myconf.get('smtp.login')
mail.settings.tls = myconf.get('smtp.tls') or False
mail.settings.ssl = myconf.get('smtp.ssl') or False

# -------------------------------------------------------------------------
# configure auth policy
# -------------------------------------------------------------------------
auth.settings.registration_requires_verification = False
auth.settings.registration_requires_approval = False
auth.settings.reset_password_requires_verification = True

# -------------------------------------------------------------------------
# Define your tables below (or better in another model file) for example
#
Expand Down
18 changes: 18 additions & 0 deletions applications/Rekall/models/iam.py
@@ -1,3 +1,5 @@
import datetime

from api import users
from api import types
from api import dal
Expand All @@ -9,3 +11,19 @@
Field('user'),
Field('conditions', type=dal.SerializerType(
types.IAMCondition)))

db.define_table('notifications',
Field('user'),
Field("from_user"),
Field('timestamp', type="datetime",
default=datetime.datetime.utcnow),

# Ensure that messages can not have arbitrary HTML in them. The
# full message will be expanded by the function
# rekall.templates.render_message(message_id, args).
Field('message_id', comment="The message type. Note that "
"messages must be one of a small set of templates which "
"will be expanded in JS."),

Field('args', type=dal.JSONType),
Field('read', type='boolean', default=False))
7 changes: 7 additions & 0 deletions applications/Rekall/models/menu.py
Expand Up @@ -51,3 +51,10 @@
(T('Manage Users'), False, URL(c="users", f="manage")),
(T('Add new User'), False, URL(c="users", f="add"))
]))


response.right_menu = [
(users.get_current_username(), False, "#", [
(T('Logout'), False, URL('default', 'logout'), []),
])
]
10 changes: 5 additions & 5 deletions applications/Rekall/models/rekall_client.py
@@ -1,4 +1,7 @@
import datetime
from api import dal
from rekall_lib.types import agent


db.define_table('clients',
Field('client_id', unique=True),
Expand Down Expand Up @@ -41,13 +44,11 @@
Field('timestamp', type="datetime",
default=datetime.datetime.utcnow,
comment="When the flow was created"),
Field('state', default='scheduled',
comment='Current state of the flow'),
Field('flow', type='json',
Field('flow', type=dal.SerializerType(agent.Flow),
comment="Flow to be sent to the client."),
Field('creator', type='string',
comment="Username that created the flow"),
Field('status', type='json',
Field('status', type=dal.SerializerType(agent.FlowStatus),
comment="The latest Flow status."),
)

Expand Down Expand Up @@ -84,5 +85,4 @@


from gluon import current
current.auth = auth
current.db = db

0 comments on commit 9e51d9f

Please sign in to comment.