# -*- coding: utf-8 -*- # _protocol.py # Copyright (C) 2014-2015 LEAP # # 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, either version 3 of the License, or # (at your option) any later version. # # 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 . """ Bonafide protocol. """ import os.path from collections import defaultdict from leap.bitmask.bonafide import config from leap.bitmask.bonafide.provider import Api from leap.bitmask.bonafide.session import Session, OK from leap.common.config import get_path_prefix from twisted.cred.credentials import UsernamePassword, Anonymous from twisted.cred.checkers import ANONYMOUS from twisted.internet.defer import fail from twisted.logger import Logger COMMANDS = 'signup', 'authenticate', 'logout', 'stats' _preffix = os.path.join(get_path_prefix(), 'leap') class BonafideProtocol(object): """ Expose the protocol that interacts with the Bonafide Service API. """ _apis = defaultdict(None) _sessions = defaultdict(None) log = Logger() def _get_api(self, provider): if provider.domain in self._apis: return self._apis[provider.domain] api = Api(provider.api_uri, provider.version) self._apis[provider.domain] = api return api def _get_or_create_session(self, provider, full_id, password=""): if full_id in self._sessions: return self._sessions[full_id] if full_id == ANONYMOUS: credentials = Anonymous() provider_id = provider.domain else: username, provider_id = config.get_username_and_provider( full_id) credentials = UsernamePassword(username, password) api = self._get_api(provider) provider_pem = config.get_ca_cert_path(_preffix, provider_id) session = Session(credentials, api, provider_pem) self._sessions[full_id] = session return session def _del_session_errback(self, failure, full_id): if full_id in self._sessions: del self._sessions[full_id] return failure # Service public methods def do_signup(self, full_id, password, invite=None, autoconf=False): self.log.debug('SIGNUP for %s' % full_id) _, provider_id = config.get_username_and_provider(full_id) provider = config.Provider.get(provider_id, autoconf=autoconf) d = provider.callWhenReady( self._do_signup, provider, full_id, password, invite) return d def _do_signup(self, provider, full_id, password, invite): # XXX check it's unauthenticated def return_user(result): return_code, user = result if return_code == OK: return user username, _ = config.get_username_and_provider(full_id) session = self._get_or_create_session(provider, full_id, password) d = session.signup(username, password, invite) d.addCallback(return_user) d.addErrback(self._del_session_errback, full_id) return d def do_authenticate(self, full_id, password, autoconf=False): _, provider_id = config.get_username_and_provider(full_id) provider = config.Provider.get(provider_id, autoconf=autoconf) def maybe_finish_provider_bootstrap(result): session = self._get_or_create_session(provider, full_id, password) d = provider.download_services_config_with_auth(session) d.addCallback(lambda _: result) return d d = provider.callWhenReady( self._do_authenticate, provider, full_id, password) d.addCallback(maybe_finish_provider_bootstrap) return d def _do_authenticate(self, provider, full_id, password): def return_token_and_uuid(result, _session): if result == OK: # TODO -- turn this into JSON response return str(_session.token), str(_session.uuid) self.log.debug('AUTH for %s' % full_id) session = self._get_or_create_session(provider, full_id, password) d = session.authenticate() d.addCallback(return_token_and_uuid, session) d.addErrback(self._del_session_errback, full_id) return d def do_logout(self, full_id): # XXX use the AVATAR here self.log.debug('LOGOUT for %s' % full_id) if (full_id not in self._sessions or not self._sessions[full_id].is_authenticated): return fail(RuntimeError("There is no session for such user")) session = self._sessions[full_id] d = session.logout() d.addCallback(lambda _: self._sessions.pop(full_id)) d.addCallback(lambda _: '%s logged out' % full_id) return d def do_list_users(self): users = [] for user, session in self._sessions.items(): users.append({'userid': user, 'authenticated': session.is_authenticated}) return users def do_change_password(self, full_id, current_password, new_password): self.log.debug('Change password for %s' % full_id) if (full_id not in self._sessions or not self._sessions[full_id].is_authenticated): return fail(RuntimeError("There is no session for such user")) session = self._sessions[full_id] if current_password != session.password: return fail(RuntimeError("The current password is not valid")) return session.change_password(new_password) def do_get_provider(self, provider_id, autoconf=False): provider = config.Provider.get(provider_id, autoconf=autoconf) return provider.callWhenMainConfigReady(provider.config) def do_get_service(self, provider_id, service, autoconf=False): provider = config.Provider.get(provider_id, autoconf=autoconf) return provider.callWhenMainConfigReady(provider.config, service) def do_provider_delete(self, provider_id): return config.delete_provider(provider_id) def do_provider_list(self, seeded=False): providers = config.list_providers() return [{"domain": p} for p in providers] def do_get_smtp_cert(self, full_id): if (full_id not in self._sessions or not self._sessions[full_id].is_authenticated): return fail(RuntimeError("There is no session for such user")) d = self._sessions[full_id].get_smtp_cert() return d def do_get_vpn_cert(self, full_id, anonymous=False): if anonymous: _, provider_id = full_id.split('@') provider = config.Provider.get(provider_id, autoconf=True) d = self._get_or_create_session( provider, ANONYMOUS).get_vpn_cert() else: if (full_id not in self._sessions or not self._sessions[full_id].is_authenticated): return fail(RuntimeError("There is no session for such user")) d = self._sessions[full_id].get_vpn_cert() return d def do_update_user(self): # FIXME to be implemented pass