Source code for janua.ws.services

# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
#
# Copyright (c) 2016 Cédric Clerget - HPC Center of Franche-Comté University
#
# This file is part of Janua-SMS
#
# http://github.com/mesocentrefc/Janua-SMS
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation v2.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
#

import calendar
import os
import uuid

from flask import __version__ as flask_version
from flask import Flask, jsonify
from flask import make_response, request, url_for
from flask import redirect, render_template

from flask_restless import APIManager

from datetime import timedelta, datetime

from janua import config, jdb, januapath
from janua.actions import ActionError, ActionNotifyError
from janua.actions.action import available_action_dict, get_custom_action_repr
from janua.actions.action import WebInit
from janua.utils.utilities import get_role

from janua.ws import json_error, json_success
from janua.ws.auth import serialize_token, token_serializer
from janua.ws.auth import flask_secret_key, auth_manager
from janua.ws.auth import auth_required, authenticate_admin

#
# REST API
#
from janua.ws.api.base import register_api
from janua.ws.api.sms import Sms
from janua.ws.api.contact import Contact
from janua.ws.api.group import Group
from janua.ws.api.contact_group import ContactGroup
from janua.ws.api.admin import Admin
from janua.ws.api.action import Action
from janua.ws.api.authorized_group_action import AuthorizedGroupAction
from janua.ws.api.authorized_supervisor_action import AuthorizedSupervisorAction
from janua.ws.api.contact_notify_action import ContactNotifyAction

static_folder = os.path.join(januapath, 'janua-web')
app = Flask(__name__, static_url_path='', static_folder=static_folder)
app.secret_key = flask_secret_key

manager = APIManager(app, session=jdb.session)
register_api(manager, Sms)
register_api(manager, Contact)
register_api(manager, Group)
register_api(manager, ContactGroup)
register_api(manager, Admin)
register_api(manager, Action)
register_api(manager, AuthorizedGroupAction)
register_api(manager, AuthorizedSupervisorAction)
register_api(manager, ContactNotifyAction)

def action(admin, action_name, *args, **kwargs):
    """
    Action wrapper for web context
    """
    action_dict = available_action_dict(include_keyword=False)

    arguments = {}
    if not request.json and request.method == 'POST':
        return json_error('Not in json format')
    if request.method == 'POST':
        arguments = request.json

    try:
        action = action_dict[action_name]()
        action.phone_number = admin.phone_number
        action.email = admin.email
        ret = action.call_web_context(arguments, *args, **kwargs)
    except ActionError, err:
        return json_error('Failed to trigger action: %s' % err)
    except (ValueError, TypeError):
        return json_error('web context method for %s return an invalid value' % action_name)
    except ActionNotifyError, err:
        return json_error('Notify method failed: %s' % err)
    except Exception, err:
        return json_error('Bug in action %s: %s' % (action.get_name(), err))

    error_msg = action.process_notify()
    del action
    if error_msg:
        return json_error(error_msg)

    return ret

def urlconfig(url, role=['admin', 'supervisor']):
    """
    Web action context decorator

    :param url: relative url (ex: /login)
    :param role: list of authorized role, by default all are authorized
    """
    def wrap(func):
        def init_callback(action_name, method):
            app.add_url_rule(
                url,
                action_name,
                auth_required(role=role, action_name=action_name)(action),
                methods=[method]
            )
        return WebInit(func, init_callback)
    return wrap

def get_url():
    """
    Get Janua-SMS url

    :returns: janua url
    """
    if config.web.url:
        if config.web.url[-1] != '/':
            return '%s/' % config.web.url
        return config.web.url

    port = int(config.web.port)
    hostname = config.web.hostname
    if config.web.secure_connection:
        url = 'https://'
    else:
        url = 'http://'

    if port != 443 or port != 80:
        url += '%s:%s/' % (hostname, port)
    else:
        url += '%s/' % hostname

    return url

@app.route('/')
def root():
    """
    Web server root
    """
    if config.web.secure_connection:
        return redirect(url_for('index', _external=True, _scheme='https'))
    else:
        return redirect(url_for('index'))

@app.route('/index.html')
def index():
    """
    Serve web interface
    """
    return app.send_static_file('index.html')

@app.route('/login', methods=['POST'])
[docs]def login(): """ Get an authentication session Sample request to authenticate: .. code-block:: javascript POST /login HTTP/1.1 Host: janua.mydomain.com Content-Type: application/json { "username": "admin", "password": "admin", "language": "EN", } Sample response: .. code-block:: javascript HTTP/1.1 200 { "success": true, "message": "Successful authentication", "JanuaAuthToken": "abcdef123456789", } """ if not request.json: return make_response(json_error('Request format is not json')) if 'username' not in request.json: return make_response(json_error('Username is missing')) if 'password' not in request.json: return make_response(json_error('Password is missing')) if 'language' not in request.json: return make_response(json_error('Language is missing')) username = request.json['username'] password = request.json['password'] language = request.json['language'] admin = authenticate_admin(username, password) if admin: admin_token = serialize_token(admin.id, token_serializer) if not admin_token: return make_response(json_error('Failed to generate token')) session_lifetime = timedelta(hours=config.web.session_lifetime) expire = datetime.utcnow() + session_lifetime response = make_response(json_success('Authentication ok', JanuaAuthToken=admin_token)) response.set_cookie('role', get_role(admin), expires=expire) response.set_cookie('admin_id', str(admin.id), expires=expire) response.set_cookie('auth_token', admin_token, expires=expire) return response return make_response(json_error('Authentication failure'))
@app.route('/logout', methods=['GET'])
[docs]def logout(): """ Logout session Sample request to authenticate: .. code-block:: javascript GET /logout HTTP/1.1 Host: janua.mydomain.com Content-Type: application/json Sample response: .. code-block:: http HTTP/1.1 200 """ if config.web.secure_connection: url = url_for('index', _external=True, _scheme='https') else: url = url_for('index') response = make_response(redirect(url)) response.set_cookie('role', '', expires=0) response.set_cookie('admin_id', '', expires=0) response.set_cookie('auth_token', '', expires=0) return response