1#!/usr/bin/env python 2# encoding: utf8 3# 4# Copyright © Burak Arslan <burak at arskom dot com dot tr>, 5# Arskom Ltd. http://www.arskom.com.tr 6# All rights reserved. 7# 8# Redistribution and use in source and binary forms, with or without 9# modification, are permitted provided that the following conditions are met: 10# 11# 1. Redistributions of source code must retain the above copyright notice, 12# this list of conditions and the following disclaimer. 13# 2. Redistributions in binary form must reproduce the above copyright 14# notice, this list of conditions and the following disclaimer in the 15# documentation and/or other materials provided with the distribution. 16# 3. Neither the name of the owner nor the names of its contributors may be 17# used to endorse or promote products derived from this software without 18# specific prior written permission. 19# 20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY DIRECT, 24# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 25# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 27# OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 28# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, 29# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30# 31 32 33import logging 34import random 35import sys 36 37# bcrypt seems to be among the latest consensus around cryptograpic circles on 38# storing passwords. 39# You need the package from http://code.google.com/p/py-bcrypt/ 40# You can install it by running easy_install py-bcrypt. 41try: 42 import bcrypt 43except ImportError: 44 print('easy_install --user py-bcrypt to get it.') 45 raise 46 47from spyne.application import Application 48from spyne.decorator import rpc 49from spyne.error import ArgumentError 50from spyne.model.complex import ComplexModel 51from spyne.model.fault import Fault 52from spyne.model.primitive import Mandatory 53from spyne.model.primitive import String 54from spyne.protocol.soap import Soap11 55from spyne.server.wsgi import WsgiApplication 56from spyne.service import Service 57 58 59class PublicKeyError(Fault): 60 __namespace__ = 'spyne.examples.authentication' 61 62 def __init__(self, value): 63 super(PublicKeyError, self).__init__( 64 faultstring='Value %r not found' % value) 65 66 67class AuthenticationError(Fault): 68 __namespace__ = 'spyne.examples.authentication' 69 70 def __init__(self, user_name): 71 # TODO: self.transport.http.resp_code = HTTP_401 72 73 super(AuthenticationError, self).__init__( 74 faultcode='Client.AuthenticationError', 75 faultstring='Invalid authentication request for %r' % user_name) 76 77 78class AuthorizationError(Fault): 79 __namespace__ = 'spyne.examples.authentication' 80 81 def __init__(self): 82 # TODO: self.transport.http.resp_code = HTTP_401 83 84 super(AuthorizationError, self).__init__( 85 faultcode='Client.AuthorizationError', 86 faultstring='You are not authozied to access this resource.') 87 88 89class SpyneDict(dict): 90 def __getitem__(self, key): 91 try: 92 return dict.__getitem__(self, key) 93 except KeyError: 94 raise PublicKeyError(key) 95 96 97class RequestHeader(ComplexModel): 98 __namespace__ = 'spyne.examples.authentication' 99 100 session_id = Mandatory.String 101 user_name = Mandatory.String 102 103 104class Preferences(ComplexModel): 105 __namespace__ = 'spyne.examples.authentication' 106 107 language = String(max_len=2) 108 time_zone = String 109 110 111user_db = { 112 'neo': bcrypt.hashpw('Wh1teR@bbit', bcrypt.gensalt()), 113} 114 115session_db = set() 116 117preferences_db = SpyneDict({ 118 'neo': Preferences(language='en', time_zone='Underground/Zion'), 119 'smith': Preferences(language='xx', time_zone='Matrix/Core'), 120}) 121 122 123class AuthenticationService(Service): 124 __tns__ = 'spyne.examples.authentication' 125 126 @rpc(Mandatory.String, Mandatory.String, _returns=String, 127 _throws=AuthenticationError) 128 def authenticate(ctx, user_name, password): 129 password_hash = user_db.get(user_name, None) 130 131 if password_hash is None: 132 raise AuthenticationError(user_name) 133 134 if bcrypt.hashpw(password, password_hash) == password_hash: 135 session_id = (user_name, 136 '%x' % random.randint(1 << 124, (1 << 128) - 1)) 137 session_db.add(session_id) 138 139 else: 140 raise AuthenticationError(user_name) 141 142 return session_id[1] 143 144 145class UserService(Service): 146 __tns__ = 'spyne.examples.authentication' 147 __in_header__ = RequestHeader 148 149 @rpc(Mandatory.String, _throws=PublicKeyError, _returns=Preferences) 150 def get_preferences(ctx, user_name): 151 if user_name == 'smith': 152 raise AuthorizationError() 153 154 retval = preferences_db[user_name] 155 156 return retval 157 158 159def _on_method_call(ctx): 160 if ctx.in_object is None: 161 raise ArgumentError("RequestHeader is null") 162 if not (ctx.in_header.user_name, ctx.in_header.session_id) in session_db: 163 raise AuthenticationError(ctx.in_object.user_name) 164 165 166UserService.event_manager.add_listener('method_call', _on_method_call) 167 168if __name__ == '__main__': 169 from spyne.util.wsgi_wrapper import run_twisted 170 171 logging.basicConfig(level=logging.DEBUG) 172 logging.getLogger('spyne.protocol.xml').setLevel(logging.DEBUG) 173 logging.getLogger('twisted').setLevel(logging.DEBUG) 174 175 application = Application([AuthenticationService, UserService], 176 tns='spyne.examples.authentication', 177 in_protocol=Soap11(validator='lxml'), 178 out_protocol=Soap11() 179 ) 180 181 twisted_apps = [ 182 (WsgiApplication(application), 'app'), 183 ] 184 185 sys.exit(run_twisted(twisted_apps, 8000)) 186