def update(self, title=None, category=None, difficulty=None, youtube_id=None, revision=None, **kwargs): """Update the Scratchpad and create a new ScratchpadRevision. If the either the Scratchpad or the ScratchpadRevision is invalid, the database will be unchanged. Arguments: revision: Dict of ScratchpadRevision properties passed verbatim to Scratchpad.create_revision. *: All other explicit (i.e. not **kwargs) arguments are assigned verbatim as properties to the Scratchpad, even if they hold a value of None. This is true if either None is explicitly passed in, or the default value of None is used. kwargs: Should be empty. If set, a db.BadKeyError will be thrown since someone passed in a field we weren't expecting. Note: While all keys have defaults of None, some, like revision, are actually required. This is done to to make any incomplete data throw a db.BadValueError instead of a TypeError. """ if kwargs: raise db.BadKeyError("Unexpected property " + kwargs.keys()[0]) if revision is None: raise db.BadValueError("Property revision is required") self.title = title self.category = category self.difficulty = difficulty self.youtube_id = youtube_id def save_scratchpad_and_revision(): self.put() # Convert the dict keys to strings in case they're unicode encoded # (This happens if revision came from JSON) self.create_revision(**dict_keys_to_strings(revision)) transaction_util.ensure_in_transaction(save_scratchpad_and_revision) return self
def delete(self): # Override delete so that we can also delete associated Credential # models in the same transaction. def txn(): credential = Credential.retrieve_for_user(self) if credential: credential.delete() # Delegate to the base class implementation of db.Model.delete # to do the actual deletion of this class. super(CredentialedUser, self).delete() transaction_util.ensure_in_transaction(txn)
def set_password_from_user(self, other_user): """ Sets the password for this user to be the same as that of another user. To be used sparingly! This should only be done for user migrations and other admin-related items. """ def txn(): credential = Credential.retrieve_for_user(other_user) cred_copy = None if credential: cred_copy = Credential(parent=self) cred_copy.hashed_pass = credential.hashed_pass cred_copy.salt = credential.salt self.credential_version = other_user.credential_version if cred_copy: db.put([cred_copy, self]) else: db.put(self) transaction_util.ensure_in_transaction(txn, xg_on=True)
def set_password(self, raw_password): """ Updates the password for this user and invalidates previous ones. This operation will update this UserData object as well, so any outstanding changes on it will be persisted. Authentication tokens distributed via auth/tokens.py will also be invalidated as a result of this operation (e.g. the user's auth cookie) """ new_cred_version = os.urandom(16).encode('hex') def txn(): c = Credential.retrieve_for_user(self) if c is not None: c.delete() new_cred = Credential.make_for_user(self, raw_password) self.credential_version = new_cred_version db.put([new_cred, self]) # The Remote API doesn't support queries inside transactions transaction_util.ensure_in_transaction(txn)
def post(self): valid_token, unverified_user = self.resolve_token() user_data = _resolve_user_in_https_frame(self) if not valid_token and not user_data: logging.warn("No valid token or user for /completesignup") self.redirect("/") return if valid_token: if user_data: logging.warn("Existing user is signed in, but also specified " "a valid UnverifiedUser's token. Ignoring " " existing sign-in and using token") user_data = None # Store values in a dict so we can iterate for monotonous checks. values = { 'nickname': self.request_string('nickname', default=None), 'gender': self.request_string('gender', default="unspecified"), 'username': self.request_string('username', default=None), 'password': self.request_string('password', default=None), } # Simple existence validations errors = {} for field, error in [('nickname', "Please tell us your name."), ('username', "Please pick a username."), ('password', "We need a password from you.")]: if not values[field]: errors[field] = error gender = None if values['gender']: gender = values['gender'].lower() if gender not in ['male', 'female']: gender = None if values['username']: username = values['username'] # TODO(benkomalo): ask for advice on text if user_models.UniqueUsername.is_username_too_short(username): errors['username'] = "******" elif not user_models.UniqueUsername.is_valid_username(username): errors['username'] = "******" # Only check to see if it's available if we're changing values # or if this is a brand new UserData elif ((not user_data or user_data.username != username) and not user_models.UniqueUsername.is_available_username(username)): errors['username'] = "******" if values['password']: password = values['password'] if not auth.passwords.is_sufficient_password(password, values['nickname'], values['username']): errors['password'] = "******" if len(errors) > 0: self.render_json({'errors': errors}, camel_cased=True) return if user_data: # Existing user - update their info def txn(): if (username != user_data.username and not user_data.claim_username(username)): errors['username'] = "******" return False user_data.set_password(password) user_data.update_nickname(values['nickname']) transaction_util.ensure_in_transaction(txn, xg_on=True) if len(errors) > 0: self.render_json({'errors': errors}, camel_cased=True) return else: # Converting unverified_user to a full UserData. num_tries = 0 user_data = None while not user_data and num_tries < 2: # Double-check to ensure we don't create any duplicate ids! user_id = uid.new_user_id() user_data = user_models.UserData.insert_for( user_id, unverified_user.email, username, password, birthdate=unverified_user.birthdate, gender=gender) if not user_data: self.render_json({'errors': {'username': "******"}}, camel_cased=True) return elif user_data.username != username: # Something went awry - insert_for may have returned # an existing user due to an ID collision. Try again. user_data = None num_tries += 1 if not user_data: logging.error("Tried several times to create a new user " + "unsuccessfully") self.render_json({ 'errors': {'username': "******" + "Please try again later."} }, camel_cased=True) return # Nickname is special since it requires updating external indices. user_data.update_nickname(values['nickname']) # TODO(benkomalo): move this into a transaction with the above creation unverified_user.delete() # TODO(benkomalo): give some kind of "congrats"/"welcome" notification Login.return_login_json(self, user_data, cont=user_data.profile_root)
def create(title=None, category=None, difficulty=None, youtube_id=None, user_id=None, origin_scratchpad_id=None, origin_revision_id=None, revision=None, **kwargs): """Create a new Scratchpad and ScratchpadRevision and save them to DB If either the Scratchpad or the ScratchpadRevision is invalid, the database will be unchanged. Arguments: origin_scratchpad_id: The id of the Scratchpad this Scratchpad is based off of. Used to set the origin_revision property of the Scratchpad being created. origin_revision_id: The revision of the origin Scratchpad that this Scratchpad is based on. Used to set the origin_scratchpad property of the Scratchpad being created. revision: Dict of ScratchpadRevision properties passed verbatim to Scratchpad.create_revision to create the initial revision of the Scratchpad. *: All other explicit (i.e. not **kwargs) arguments are passed verbatim to the Scratchpad constructor. kwargs: Should be empty. If set, a db.BadKeyError will be thrown since someone passed in a field we weren't expecting. Note: While all keys have defaults of None, some, like revision, are actually required. This is done to to make any incomplete data throw a db.BadValueError instead of a TypeError. """ if kwargs: raise db.BadKeyError("Unexpected property " + kwargs.keys()[0]) scratchpad = Scratchpad(title=title, category=category, difficulty=difficulty, youtube_id=youtube_id, user_id=user_id) if origin_revision_id and origin_scratchpad_id: origin_scratchpad = Scratchpad.get_by_id(origin_scratchpad_id) if origin_scratchpad is None: raise db.BadValueError("No scratchpad with id %d" % ( origin_scratchpad_id)) origin_revision = origin_scratchpad.get_revision_by_id( origin_revision_id) if origin_revision is None: raise db.BadValueError( "No revision with id %d for scratchpad %d" % ( origin_scratchpad_id, origin_revision_id)) scratchpad.origin_scratchpad = origin_scratchpad scratchpad.origin_revision = origin_revision elif origin_revision_id or origin_scratchpad_id: raise db.BadValueError( "Specified one of origin_scratchpad_id/origin_revision_id" " but not the other") if revision is None: raise db.BadValueError("Property revision is required") def save_scratchpad_and_revision(): scratchpad.put() # Convert the dict keys to strings in case they're unicode encoded # (This happens if revision came from JSON) scratchpad.create_revision(**dict_keys_to_strings(revision)) transaction_util.ensure_in_transaction(save_scratchpad_and_revision) return scratchpad
def post(self): valid_token, unverified_user = self.resolve_token() user_data = _resolve_user_in_https_frame(self) if not valid_token and not user_data: logging.warn("No valid token or user for /completesignup") self.redirect("/") return if valid_token: if user_data: logging.warn("Existing user is signed in, but also specified " "a valid UnverifiedUser's token. Ignoring " " existing sign-in and using token") user_data = None # Store values in a dict so we can iterate for monotonous checks. values = { 'nickname': self.request_string('nickname', default=None), 'gender': self.request_string('gender', default="unspecified"), 'username': self.request_string('username', default=None), 'password': self.request_string('password', default=None), } # Simple existence validations errors = {} for field, error in [('nickname', "Please tell us your name."), ('username', "Please pick a username."), ('password', "We need a password from you.")]: if not values[field]: errors[field] = error gender = None if values['gender']: gender = values['gender'].lower() if gender not in ['male', 'female']: gender = None if values['username']: username = values['username'] # TODO(benkomalo): ask for advice on text if user_models.UniqueUsername.is_username_too_short(username): errors['username'] = "******" elif not user_models.UniqueUsername.is_valid_username(username): errors[ 'username'] = "******" # Only check to see if it's available if we're changing values # or if this is a brand new UserData elif ((not user_data or user_data.username != username) and not user_models.UniqueUsername.is_available_username( username)): errors['username'] = "******" if values['password']: password = values['password'] if not auth.passwords.is_sufficient_password( password, values['nickname'], values['username']): errors['password'] = "******" if len(errors) > 0: self.render_json({'errors': errors}, camel_cased=True) return if user_data: # Existing user - update their info def txn(): if (username != user_data.username and not user_data.claim_username(username)): errors['username'] = "******" return False user_data.set_password(password) user_data.update_nickname(values['nickname']) transaction_util.ensure_in_transaction(txn, xg_on=True) if len(errors) > 0: self.render_json({'errors': errors}, camel_cased=True) return else: # Converting unverified_user to a full UserData. num_tries = 0 user_data = None while not user_data and num_tries < 2: # Double-check to ensure we don't create any duplicate ids! user_id = uid.new_user_id() user_data = user_models.UserData.insert_for( user_id, unverified_user.email, username, password, birthdate=unverified_user.birthdate, gender=gender) if not user_data: self.render_json( { 'errors': { 'username': "******" } }, camel_cased=True) return elif user_data.username != username: # Something went awry - insert_for may have returned # an existing user due to an ID collision. Try again. user_data = None num_tries += 1 if not user_data: logging.error("Tried several times to create a new user " + "unsuccessfully") self.render_json( { 'errors': { 'username': "******" + "Please try again later." } }, camel_cased=True) return # Nickname is special since it requires updating external indices. user_data.update_nickname(values['nickname']) # TODO(benkomalo): move this into a transaction with the above creation unverified_user.delete() # TODO(benkomalo): give some kind of "congrats"/"welcome" notification Login.return_login_json(self, user_data, cont=user_data.profile_root)