This repository has been archived on 2023-02-21. You can view files and clone it, but cannot push or open issues or pull requests.
ckanext-ozwillo-organizatio.../ckanext/ozwillo_organization_api/plugin.py

213 lines
7.6 KiB
Python

from hashlib import sha1
import hmac
import requests
import logging
import json
from slugify import slugify
import ckan.plugins as plugins
import ckan.plugins.toolkit as toolkit
import ckan.logic as logic
import ckan.lib.base as base
from pylons import config
from ckan.common import request, _
from ckan.logic.action.create import _group_or_org_create as group_or_org_create
from ckan.logic.action.create import user_create
from ckan.logic.action.delete import _group_or_org_purge
from ckan.lib.plugins import DefaultOrganizationForm
plugin_config_prefix = 'ckanext.ozwillo_organization_api.'
log = logging.getLogger(__name__)
def valid_signature_required(func):
signature_header_name = config.get(plugin_config_prefix + 'signature_header_name',
'X-Hub-Signature')
instantiated_secret = config.get(plugin_config_prefix + 'instantiation_secret',
'secret')
def wrapper(context, data):
if signature_header_name in request.headers:
if request.headers[signature_header_name].startswith('sha1='):
algo, received_hmac = request.headers[signature_header_name].rsplit('=')
computed_hmac = hmac.new(instantiated_secret, request.body, sha1).hexdigest()
# the received hmac is uppercase according to
# http://doc.ozwillo.com/#ref-3-2-1
if received_hmac != computed_hmac.upper():
raise logic.NotAuthorized(_('Invalid HMAC'))
else:
raise logic.ValidationError(_('Invalid HMAC algo'))
else:
raise logic.NotAuthorized(_("No HMAC in the header"))
return func(context, data)
return wrapper
@valid_signature_required
def create_organization(context, data_dict):
context['ignore_auth'] = True
model = context['model']
session = context['session']
destruction_secret = config.get(plugin_config_prefix + 'destruction_secret',
'changeme')
client_id = data_dict.pop('client_id')
client_secret = data_dict.pop('client_secret')
instance_id = data_dict.pop('instance_id')
# re-mapping received dict
registration_uri = data_dict.pop('instance_registration_uri')
organization = data_dict['organization']
user = data_dict['user']
user_dict = {
'id': user['id'],
'name': user['id'].replace('-', ''),
'email': user['email_address'],
'password': user['id']
}
user_obj = model.User.get(user_dict['name'])
org_dict = {
'type': 'organization',
'name': slugify(organization['name']),
'id': instance_id,
'title': organization['name'],
'user': user_dict['name']
}
if not user_obj:
user_create(context, user_dict)
context['user'] = user_dict['name']
try:
delete_uri = toolkit.url_for(host=request.host,
controller='api', action='action',
logic_function="delete-ozwillo-organization",
ver=context['api_version'],
qualified=True)
organization_uri = toolkit.url_for(host=request.host,
controller='organization',
action='read',
id=org_dict['name'],
qualified=True)
default_icon_url = toolkit.url_for(host=request.host,
qualified=True,
controller='home',
action='index') + 'organization_icon.png'
group_or_org_create(context, org_dict, is_org=True)
# setting organization as active explicitely
group = model.Group.get(org_dict['name'])
group.state = 'active'
group.image_url = default_icon_url
group.save()
model.repo.new_revision()
model.GroupExtra(group_id=group.id, key='client_id',
value=client_id).save()
model.GroupExtra(group_id=group.id, key='client_secret',
value=client_secret).save()
session.flush()
# notify about organization creation
services = {'services': [{
'local_id': 'organization',
'name': 'Open Data',
'service_uri': organization_uri + '/sso',
'description': 'Organization ' + org_dict['name'] + ' on CKAN',
'tos_uri': organization_uri,
'policy_uri': organization_uri,
'icon': group.image_url,
'payment_option': 'FREE',
'target_audience': ['PUBLIC_BODIES'],
'contacts': [organization_uri],
'redirect_uris': [organization_uri + '/callback'],
'post_logout_redirect_uris': [organization_uri + '/logout'],
'visible': False}],
'instance_id': instance_id,
'destruction_uri': delete_uri,
'destruction_secret': destruction_secret,
'needed_scopes': [{
'scope_id': 'profile',
'motivation': 'Used to link user to the organization'
}]
}
headers = {'Content-type': 'application/json',
'Accept': 'application/json'}
requests.post(registration_uri,
data=json.dumps(services),
auth=(client_id, client_secret),
headers=headers
)
except logic.ValidationError, e:
log.debug('Validation error "%s" occured while creating organization' % e)
raise
@valid_signature_required
def delete_organization(context, data_dict):
data_dict['id'] = data_dict.pop('instance_id')
context['ignore_auth'] = True
_group_or_org_purge(context, data_dict, is_org=True)
class OrganizationForm(plugins.SingletonPlugin, DefaultOrganizationForm):
"""
Custom form ignoring 'title' and 'name' organization fields
"""
plugins.implements(plugins.IGroupForm)
def is_fallback(self):
return True
def group_types(self):
return ('organization',)
def form_to_db_schema(self):
schema = super(OrganizationForm, self).form_to_db_schema()
del schema['name']
del schema['title']
return schema
class ErrorController(base.BaseController):
def error403(self):
return base.abort(403, '')
class OzwilloOrganizationApiPlugin(plugins.SingletonPlugin):
"""
API for OASIS to create and delete an organization
"""
plugins.implements(plugins.IActions)
plugins.implements(plugins.IConfigurer)
plugins.implements(plugins.IRoutes)
def before_map(self, map):
# disable organization and members api
for action in ('member_create', 'member_delete',
'organization_member_delete',
'organization_member_create',
'organization_create',
'organization_update',
'organization_delete'):
map.connect('/api/{ver:.*}/action/%s' % action,
controller=__name__ + ':ErrorController',
action='error403')
return map
def after_map(self, map):
return map
def update_config(self, config):
toolkit.add_template_directory(config, 'templates')
toolkit.add_public_directory(config, 'public')
def get_actions(self):
return {
'create-ozwillo-organization': create_organization,
'delete-ozwillo-organization': delete_organization
}