def construct_state_param_for_add(self, state=None, **kwargs): """Construct the state parameter if one isn't explicitly passed in. The following keys are common: - operation: 'add' or 'delete' - feature: 'listen', 'publish', or 'webmention' - callback: an optional external callback, that we will redirect to at the end of the authorization handshake - source: the source key, only applicable to deletes """ state_obj = util.decode_oauth_state(state) if not state_obj: state_obj = {field: self.request.get(field) for field in ('callback', 'feature', 'id', 'user_url')} state_obj['operation'] = 'add' if kwargs: state_obj.update(kwargs) return util.encode_oauth_state(state_obj)
def maybe_add_or_delete_source(source_cls, auth_entity, state, **kwargs): """Adds or deletes a source if auth_entity is not None. Used in each source's oauth-dropins :meth:`Callback.finish()` and :meth:`Callback.get()` methods, respectively. Args: source_cls: source class, e.g. :class:`instagram.Instagram` auth_entity: ouath-dropins auth entity state: string, OAuth callback state parameter. a JSON serialized dict with operation, feature, and an optional callback URL. For deletes, it will also include the source key kwargs: passed through to the source_cls constructor Returns: source entity if it was created or updated, otherwise None """ state_obj = util.decode_oauth_state(state) operation = state_obj.get('operation', 'add') feature = state_obj.get('feature') callback = state_obj.get('callback') user_url = state_obj.get('user_url') logger.debug( 'maybe_add_or_delete_source with operation=%s, feature=%s, callback=%s', operation, feature, callback) logins = None if operation == 'add': # this is an add/update if not auth_entity: # TODO: only show if we haven't already flashed another message? # get_flashed_messages() caches so it's dangerous to call to check; # use eg session.get('_flashes', []) instead. # https://stackoverflow.com/a/17243946/186123 flash("OK, you're not signed up. Hope you reconsider!") if callback: callback = util.add_query_params(callback, {'result': 'declined'}) logger.debug( f'user declined adding source, redirect to external callback {callback}' ) redirect(callback) else: redirect('/') logger.info( f'{source_cls.__class__.__name__}.create_new with {auth_entity.key}, {state}, {kwargs}' ) source = source_cls.create_new( auth_entity=auth_entity, features=feature.split(',') if feature else [], user_url=user_url, **kwargs) if source: # if we're normalizing username case to lower case to make the key id, check # if there's and old Source with a capitalized key id, and if so, disable it # https://github.com/snarfed/bridgy/issues/884 if source.USERNAME_KEY_ID and source.username != source.key_id(): @ndb.transactional() def maybe_disable_original(): orig = source_cls.get_by_id(source.username) if orig: logging.info( f'Disabling {orig.bridgy_url()} for lower case {source.bridgy_url()}' ) orig.features = [] orig.put() maybe_disable_original() # add to login cookie logins = get_logins() logins.append( Login(path=source.bridgy_path(), site=source.SHORT_NAME, name=source.label_name())) if callback: callback = util.add_query_params( callback, { 'result': 'success', 'user': source.bridgy_url(), 'key': source.key.urlsafe().decode(), } if source else {'result': 'failure'}) logger.debug( 'finished adding source, redirect to external callback %s', callback) redirect(callback, logins=logins) elif not source.domains: redirect('/edit-websites?' + urllib.parse.urlencode({ 'source_key': source.key.urlsafe().decode(), }), logins=logins) else: redirect(source.bridgy_url(), logins=logins) # no source redirect('/') else: # this is a delete if auth_entity: # TODO: remove from logins cookie redirect( f'/delete/finish?auth_entity={auth_entity.key.urlsafe().decode()}&state={state}' ) else: flash( f'If you want to disable, please approve the {source_cls.GR_CLASS.NAME} prompt.' ) source_key = state_obj.get('source') if source_key: source = ndb.Key(urlsafe=source_key).get() if source: redirect(source.bridgy_url()) redirect('/')
def maybe_add_or_delete_source(self, source_cls, auth_entity, state, **kwargs): """Adds or deletes a source if auth_entity is not None. Used in each source's oauth-dropins :meth:`CallbackHandler.finish()` and :meth:`CallbackHandler.get()` methods, respectively. Args: source_cls: source class, e.g. :class:`instagram.Instagram` auth_entity: ouath-dropins auth entity state: string, OAuth callback state parameter. a JSON serialized dict with operation, feature, and an optional callback URL. For deletes, it will also include the source key kwargs: passed through to the source_cls constructor Returns: source entity if it was created or updated, otherwise None """ state_obj = util.decode_oauth_state(state) operation = state_obj.get('operation', 'add') feature = state_obj.get('feature') callback = state_obj.get('callback') user_url = state_obj.get('user_url') logging.debug( 'maybe_add_or_delete_source with operation=%s, feature=%s, callback=%s', operation, feature, callback) if operation == 'add': # this is an add/update if not auth_entity: if not self.messages: self.messages.add("OK, you're not signed up. Hope you reconsider!") if callback: callback = util.add_query_params(callback, {'result': 'declined'}) logging.debug( 'user declined adding source, redirect to external callback %s', callback) # call super.redirect so the callback url is unmodified super(Handler, self).redirect(callback) else: self.redirect('/') return CachedPage.invalidate('/users') logging.info('%s.create_new with %s', source_cls.__class__.__name__, (auth_entity.key, state, kwargs)) source = source_cls.create_new(self, auth_entity=auth_entity, features=feature.split(',') if feature else [], user_url=user_url, **kwargs) if source: # add to login cookie logins = self.get_logins() logins.append(Login(path=source.bridgy_path(), site=source.SHORT_NAME, name=source.label_name())) self.set_logins(logins) if callback: callback = util.add_query_params(callback, { 'result': 'success', 'user': source.bridgy_url(self), 'key': source.key.urlsafe().decode(), } if source else {'result': 'failure'}) logging.debug( 'finished adding source, redirect to external callback %s', callback) # call super.redirect so the callback url is unmodified super(Handler, self).redirect(callback) elif source and not source.domains: self.redirect('/edit-websites?' + urllib.parse.urlencode({ 'source_key': source.key.urlsafe().decode(), })) else: self.redirect(source.bridgy_url(self) if source else '/') return source else: # this is a delete if auth_entity: self.redirect('/delete/finish?auth_entity=%s&state=%s' % (auth_entity.key.urlsafe().decode(), state)) else: self.messages.add('If you want to disable, please approve the %s prompt.' % source_cls.GR_CLASS.NAME) source_key = state_obj.get('source') if source_key: source = ndb.Key(urlsafe=source_key).get() if source: return self.redirect(source.bridgy_url(self)) self.redirect('/')
def maybe_add_or_delete_source(self, source_cls, auth_entity, state, **kwargs): """Adds or deletes a source if auth_entity is not None. Used in each source's oauth-dropins :meth:`CallbackHandler.finish()` and :meth:`CallbackHandler.get()` methods, respectively. Args: source_cls: source class, e.g. :class:`instagram.Instagram` auth_entity: ouath-dropins auth entity state: string, OAuth callback state parameter. a JSON serialized dict with operation, feature, and an optional callback URL. For deletes, it will also include the source key kwargs: passed through to the source_cls constructor Returns: source entity if it was created or updated, otherwise None """ state_obj = util.decode_oauth_state(state) operation = state_obj.get('operation', 'add') feature = state_obj.get('feature') callback = state_obj.get('callback') user_url = state_obj.get('user_url') logging.debug( 'maybe_add_or_delete_source with operation=%s, feature=%s, callback=%s', operation, feature, callback) if operation == 'add': # this is an add/update if not auth_entity: if not self.messages: self.messages.add("OK, you're not signed up. Hope you reconsider!") if callback: callback = util.add_query_params(callback, {'result': 'declined'}) logging.debug( 'user declined adding source, redirect to external callback %s', callback) # call super.redirect so the callback url is unmodified super(Handler, self).redirect(callback.encode('utf-8')) else: self.redirect('/') return CachedPage.invalidate('/users') logging.info('%s.create_new with %s', source_cls.__class__.__name__, (auth_entity.key, state, kwargs)) source = source_cls.create_new(self, auth_entity=auth_entity, features=feature.split(',') if feature else [], user_url=user_url, **kwargs) if source: # add to login cookie logins = self.get_logins() logins.append(Login(path=source.bridgy_path(), site=source.SHORT_NAME, name=source.label_name())) self.set_logins(logins) if callback: callback = util.add_query_params(callback, { 'result': 'success', 'user': source.bridgy_url(self), 'key': source.key.urlsafe(), } if source else {'result': 'failure'}) logging.debug( 'finished adding source, redirect to external callback %s', callback) # call super.redirect so the callback url is unmodified super(Handler, self).redirect(callback.encode('utf-8')) elif source and not source.domains: self.redirect('/edit-websites?' + urllib.urlencode({ 'source_key': source.key.urlsafe(), })) else: self.redirect(source.bridgy_url(self) if source else '/') return source else: # this is a delete if auth_entity: self.redirect('/delete/finish?auth_entity=%s&state=%s' % (auth_entity.key.urlsafe(), state)) else: self.messages.add('If you want to disable, please approve the %s prompt.' % source_cls.GR_CLASS.NAME) source_key = state_obj.get('source') if source_key: source = ndb.Key(urlsafe=source_key).get() if source: return self.redirect(source.bridgy_url(self)) self.redirect('/')