class SSOLogin(dbus.service.Object): """Login thru the Single Sign On service.""" # Operator not preceded by a space (fails with dbus decorators) # pylint: disable=C0322 def __init__(self, bus_name, object_path=DBUS_ACCOUNT_PATH, sso_login_processor_class=Account, sso_service_class=None): """Initiate the Login object.""" dbus.service.Object.__init__(self, object_path=object_path, bus_name=bus_name) self.root = SSOLoginRoot(sso_login_processor_class, sso_service_class) msg = 'Use ubuntu_sso.main.CredentialsManagement instead.' warnings.warn(msg, DeprecationWarning) # generate_capcha signals @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss") def CaptchaGenerated(self, app_name, result): """Signal thrown after the captcha is generated.""" logger.debug('SSOLogin: emitting CaptchaGenerated with app_name "%s" ' 'and result %r', app_name, result) @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="sa{ss}") def CaptchaGenerationError(self, app_name, error): """Signal thrown when there's a problem generating the captcha.""" logger.debug('SSOLogin: emitting CaptchaGenerationError with ' 'app_name "%s" and error %r', app_name, error) @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME, in_signature='ss') def generate_captcha(self, app_name, filename): """Call the matching method in the processor.""" self.root.generate_captcha(app_name, filename, self.CaptchaGenerated, self.CaptchaGenerationError) # register_user signals @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss") def UserRegistered(self, app_name, result): """Signal thrown when the user is registered.""" logger.debug('SSOLogin: emitting UserRegistered with app_name "%s" ' 'and result %r', app_name, result) @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="sa{ss}") def UserRegistrationError(self, app_name, error): """Signal thrown when there's a problem registering the user.""" logger.debug('SSOLogin: emitting UserRegistrationError with ' 'app_name "%s" and error %r', app_name, error) @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME, in_signature='ssssss') def register_user(self, app_name, email, password, name, captcha_id, captcha_solution): """Call the matching method in the processor.""" self.root.register_user(app_name, email, password, name, captcha_id, captcha_solution, self.UserRegistered, self.UserRegistrationError) # login signals @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss") def LoggedIn(self, app_name, result): """Signal thrown when the user is logged in.""" logger.debug('SSOLogin: emitting LoggedIn with app_name "%s" ' 'and result %r', app_name, result) @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="sa{ss}") def LoginError(self, app_name, error): """Signal thrown when there is a problem in the login.""" logger.debug('SSOLogin: emitting LoginError with ' 'app_name "%s" and error %r', app_name, error) @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss") def UserNotValidated(self, app_name, result): """Signal thrown when the user is not validated.""" logger.debug('SSOLogin: emitting UserNotValidated with app_name "%s" ' 'and result %r', app_name, result) @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME, in_signature='sss') def login(self, app_name, email, password): """Call the matching method in the processor.""" self.root.login(app_name, email, password, self.LoggedIn, self.LoginError, self.UserNotValidated) # validate_email signals @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss") def EmailValidated(self, app_name, result): """Signal thrown after the email is validated.""" logger.debug('SSOLogin: emitting EmailValidated with app_name "%s" ' 'and result %r', app_name, result) @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="sa{ss}") def EmailValidationError(self, app_name, error): """Signal thrown when there's a problem validating the email.""" logger.debug('SSOLogin: emitting EmailValidationError with ' 'app_name "%s" and error %r', app_name, error) @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME, in_signature='ssss') def validate_email(self, app_name, email, password, email_token): """Call the matching method in the processor.""" self.root.validate_email(app_name, email, password, email_token, self.EmailValidated, self.EmailValidationError) # request_password_reset_token signals @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss") def PasswordResetTokenSent(self, app_name, result): """Signal thrown when the token is succesfully sent.""" logger.debug('SSOLogin: emitting PasswordResetTokenSent with app_name ' '"%s" and result %r', app_name, result) @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="sa{ss}") def PasswordResetError(self, app_name, error): """Signal thrown when there's a problem sending the token.""" logger.debug('SSOLogin: emitting PasswordResetError with ' 'app_name "%s" and error %r', app_name, error) @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME, in_signature='ss') def request_password_reset_token(self, app_name, email): """Call the matching method in the processor.""" self.root.request_password_reset_token(app_name, email, self.PasswordResetTokenSent, self.PasswordResetError) # set_new_password signals @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="ss") def PasswordChanged(self, app_name, result): """Signal thrown when the token is succesfully sent.""" logger.debug('SSOLogin: emitting PasswordChanged with app_name "%s" ' 'and result %r', app_name, result) @dbus.service.signal(DBUS_IFACE_USER_NAME, signature="sa{ss}") def PasswordChangeError(self, app_name, error): """Signal thrown when there's a problem sending the token.""" logger.debug('SSOLogin: emitting PasswordChangeError with ' 'app_name "%s" and error %r', app_name, error) @dbus.service.method(dbus_interface=DBUS_IFACE_USER_NAME, in_signature='ssss') def set_new_password(self, app_name, email, token, new_password): """Call the matching method in the processor.""" self.root.set_new_password(app_name, email, token, new_password, self.PasswordChanged, self.PasswordChangeError)
class SSOLogin(Referenceable, SignalBroadcaster): """Login thru the Single Sign On service.""" __metaclass__ = RemoteMeta # calls that will be accessible remotely remote_calls = [ 'generate_captcha', 'register_user', 'login', 'validate_email', 'request_password_reset_token', 'set_new_password'] def __init__(self, bus_name, object_path=None, sso_login_processor_class=Account, sso_service_class=None): """Initiate the Login object.""" super(SSOLogin, self).__init__() # ignore bus_name and object path so that we do not break the current # API. Shall we change this??? self.root = SSOLoginRoot(sso_login_processor_class, sso_service_class) # generate_capcha signals def emit_captcha_generated(self, app_name, result): """Signal thrown after the captcha is generated.""" logger.debug('SSOLogin: emitting CaptchaGenerated with app_name "%s" ' 'and result %r', app_name, result) self.emit_signal('on_captcha_generated', app_name, result) def emit_captcha_generation_error(self, app_name, raised_error): """Signal thrown when there's a problem generating the captcha.""" logger.debug('SSOLogin: emitting CaptchaGenerationError with ' 'app_name "%s" and error %r', app_name, raised_error) self.emit_signal('on_captcha_generation_error', app_name, except_to_errdict(raised_error.value)) def generate_captcha(self, app_name, filename): """Call the matching method in the processor.""" self.root.generate_captcha(app_name, filename, self.emit_captcha_generated, self.emit_captcha_generation_error) # register_user signals def emit_user_registered(self, app_name, result): """Signal thrown when the user is registered.""" logger.debug('SSOLogin: emitting UserRegistered with app_name "%s" ' 'and result %r', app_name, result) self.emit_signal('on_user_registered', app_name, result) def emit_user_registration_error(self, app_name, raised_error): """Signal thrown when there's a problem registering the user.""" logger.debug('SSOLogin: emitting UserRegistrationError with ' 'app_name "%s" and error %r', app_name, raised_error) self.emit_signal('on_user_registration_error', app_name, except_to_errdict(raised_error.value)) def register_user(self, app_name, email, password, displayname, captcha_id, captcha_solution): """Call the matching method in the processor.""" self.root.register_user(app_name, email, password, displayname, captcha_id, captcha_solution, self.emit_user_registered, self.emit_user_registration_error) # login signals def emit_logged_in(self, app_name, result): """Signal thrown when the user is logged in.""" logger.debug('SSOLogin: emitting LoggedIn with app_name "%s" ' 'and result %r', app_name, result) self.emit_signal('on_logged_in', app_name, result) def emit_login_error(self, app_name, raised_error): """Signal thrown when there is a problem in the login.""" logger.debug('SSOLogin: emitting LoginError with ' 'app_name "%s" and error %r', app_name, raised_error) self.emit_signal('on_login_error', app_name, except_to_errdict(raised_error.value)) def emit_user_not_validated(self, app_name, result): """Signal thrown when the user is not validated.""" logger.debug('SSOLogin: emitting UserNotValidated with app_name "%s" ' 'and result %r', app_name, result) self.emit_signal('on_user_not_validated', app_name, result) def login(self, app_name, email, password): """Call the matching method in the processor.""" self.root.login(app_name, email, password, self.emit_logged_in, self.emit_login_error, self.emit_user_not_validated) # validate_email signals def emit_email_validated(self, app_name, result): """Signal thrown after the email is validated.""" logger.debug('SSOLogin: emitting EmailValidated with app_name "%s" ' 'and result %r', app_name, result) self.emit_signal('on_email_validated', app_name, result) def emit_email_validation_error(self, app_name, raised_error): """Signal thrown when there's a problem validating the email.""" logger.debug('SSOLogin: emitting EmailValidationError with ' 'app_name "%s" and error %r', app_name, raised_error) self.emit_signal('on_email_validation_error', app_name, except_to_errdict(raised_error.value)) def validate_email(self, app_name, email, password, email_token): """Call the matching method in the processor.""" self.root.validate_email(app_name, email, password, email_token, self.emit_email_validated, self.emit_email_validation_error) # request_password_reset_token signals def emit_password_reset_token_sent(self, app_name, result): """Signal thrown when the token is successfully sent.""" logger.debug('SSOLogin: emitting PasswordResetTokenSent with app_name ' '"%s" and result %r', app_name, result) self.emit_signal('on_password_reset_token_sent', app_name, result) def emit_password_reset_error(self, app_name, raised_error): """Signal thrown when there's a problem sending the token.""" logger.debug('SSOLogin: emitting PasswordResetError with ' 'app_name "%s" and error %r', app_name, raised_error) self.emit_signal('on_password_reset_error', app_name, except_to_errdict(raised_error.value)) def request_password_reset_token(self, app_name, email): """Call the matching method in the processor.""" self.root.request_password_reset_token(app_name, email, self.emit_password_reset_token_sent, self.emit_password_reset_error) # set_new_password signals def emit_password_changed(self, app_name, result): """Signal thrown when the token is successfully sent.""" logger.debug('SSOLogin: emitting PasswordChanged with app_name "%s" ' 'and result %r', app_name, result) self.emit_signal('on_password_changed', app_name, result) def emit_password_change_error(self, app_name, raised_error): """Signal thrown when there's a problem sending the token.""" logger.debug('SSOLogin: emitting PasswordChangeError with ' 'app_name "%s" and error %r', app_name, raised_error) self.emit_signal('on_password_change_error', app_name, except_to_errdict(raised_error.value)) def set_new_password(self, app_name, email, token, new_password): """Call the matching method in the processor.""" self.root.set_new_password(app_name, email, token, new_password, self.emit_password_changed, self.emit_password_change_error)
class Credentials(object): """Credentials management gateway.""" def __init__(self, app_name, tc_url=None, help_text='', window_id=0, ping_url=None, ui_module='ubuntu_sso.gtk.gui', ui_class='UbuntuSSOClientGUI', success_cb=NO_OP, error_cb=NO_OP, denial_cb=NO_OP): """Return a Credentials management object. 'app_name' is the application name to be displayed in the GUI. 'tc_url' is the URL pointing to Terms & Conditions. If None, no TOS agreement will be displayed. 'help_text' is an explanatory text for the end-users, will be shown below the headers. 'window_id' is the id of the window which will be set as a parent of the GUI. If 0, no parent will be set. 'ping_url' is the url that will be pinged when a user registers/logins successfully. The user email will be attached to 'ping_url'. 'success_cb' will be called when the credentials were retrieved successfully. Two params will be passed: the app_name and the credentials per se. The credentials is a dictionary of the form: {'token': <value>, 'token_secret': <value>, 'consumer_key': <value>, 'consumer_secret': <value>, 'name': <the token name, matches "[app_name] @ [host name]">} 'error_cb' will be called when the credentials retrieval failed. Two params will be passed: the app_name, and an error dict with 2 keys: the error message (user friendly, not translatable so far), and the detailed error (usually the traceback). 'denial_cb' will be called when the user denied the credentials to the caller. A single param is passed: the app_name. """ self.app_name = app_name self.help_text = help_text self.window_id = window_id self.ping_url = ping_url self.tc_url = tc_url self.ui_module = ui_module self.ui_class = ui_class self._success_cb = success_cb self._error_cb = error_cb self.denial_cb = denial_cb self.inner = None # will hold the GUI or SSOLoginRoot instance @handle_failures(msg='Problem while retrieving credentials') @inlineCallbacks def _login_success_cb(self, app_name, email): """Store credentials when the login/registration succeeded. Also, open self.ping_url/email to notify about this new token. If any error occur, self.error_cb is called. Otherwise, self.success_cb is called. Return 0 on success, and a non-zero value (or None) on error. """ logger.info('Login/registration was successful for app %r, email %r', app_name, email) creds = yield self.find_credentials() if creds is not None: assert len(creds) > 0, 'Creds are empty! This should not happen' # ping a server with the credentials if we were requested to if self.ping_url is not None: status = yield self._ping_url(app_name, email, creds) if status is None: yield self.clear_credentials() return self.success_cb(creds) returnValue(0) def _auth_denial_cb(self, app_name): """The user decided not to allow the registration or login.""" logger.warning('Login/registration was denied to app %r', app_name) self.denial_cb(app_name) @handle_failures(msg='Problem opening the ping_url') @inlineCallbacks def _ping_url(self, app_name, email, credentials): """Ping the self.ping_url with the email attached. Sign the request with the user credentials. The self.ping_url must be defined if this method is being called. """ logger.info('Pinging server for app_name %r, ping_url: %r, ' 'email %r.', app_name, self.ping_url, email) try: url = self.ping_url.format(email=email) except IndexError: # tuple index out of range url = self.ping_url.format(email) # format the first substitution if url == self.ping_url: logger.debug('Original url (%r) could not be formatted, ' 'appending email (%r).', self.ping_url, email) url = self.ping_url + email headers = utils.oauth_headers(url, credentials) request = urllib2.Request(url, headers=headers) logger.debug('Opening the url %r with urllib2.urlopen.', request.get_full_url()) # This code is blocking, we should change this. # I've tried with deferToThread an twisted.web.client.getPage # but the returned deferred will never be fired (nataliabidart). response = urllib2.urlopen(request) logger.debug('Url opened. Response: %s.', response.code) returnValue(response) @handle_exceptions(msg='Problem opening the Ubuntu SSO user interface') def _show_ui(self, login_only): """Shows the UI, connect outcome signals.""" __import__(self.ui_module) gui = sys.modules[self.ui_module] self.inner = getattr(gui, self.ui_class)(app_name=self.app_name, tc_url=self.tc_url, help_text=self.help_text, window_id=self.window_id, login_only=login_only) self.inner.login_success_callback = self._login_success_cb self.inner.registration_success_callback = self._login_success_cb self.inner.user_cancellation_callback = self._auth_denial_cb @handle_exceptions(msg='Problem logging with email and password.') def _do_login(self, email, password): """Login using email/password, connect outcome signals.""" from ubuntu_sso.main import SSOLoginRoot self.inner = SSOLoginRoot() self.inner.login(app_name=self.app_name, email=email, password=password, result_cb=self._login_success_cb, error_cb=self._error_cb, not_validated_cb=self._error_cb) @handle_failures(msg='Problem while retrieving credentials') @inlineCallbacks def _login_or_register(self, login_only, email=None, password=None): """Get credentials if found else prompt the GUI.""" logger.info("_login_or_register: login_only=%r email=%r.", login_only, email) token = yield self.find_credentials() if token is not None and len(token) > 0: self.success_cb(token) elif token == {}: if email and password: self._do_login(email, password) else: self._show_ui(login_only) else: # something went wrong with find_credentials, already handled. logger.info('_login_or_register: call to "find_credentials" went ' 'wrong, and was already handled.') def error_cb(self, error_dict): """Handle error and notify the caller.""" logger.error('Calling error callback at %r (error is %r).', self._error_cb, error_dict) self._error_cb(self.app_name, error_dict) def success_cb(self, creds): """Handle success and notify the caller.""" logger.debug('Calling success callback at %r.', self._success_cb) self._success_cb(self.app_name, creds) @inlineCallbacks def find_credentials(self): """Get the credentials for 'self.app_name'. Return {} if not there.""" creds = yield Keyring().get_credentials(self.app_name) logger.info('find_credentials: self.app_name %r, ' 'result is {}? %s', self.app_name, creds is None) returnValue(creds if creds is not None else {}) @inlineCallbacks def clear_credentials(self): """Clear the credentials for 'self.app_name'.""" yield Keyring().delete_credentials(self.app_name) @inlineCallbacks def store_credentials(self, token): """Store the credentials for 'self.app_name'.""" yield Keyring().set_credentials(self.app_name, token) def register(self): """Get credentials if found else prompt the GUI to register.""" return self._login_or_register(login_only=False) def login(self): """Get credentials if found else prompt the GUI to login.""" return self._login_or_register(login_only=True) def login_email_password(self, email, password): """Get credentials if found else login using email and password.""" return self._login_or_register(login_only=True, email=email, password=password)