app/soc/logic/site/id_user.py
changeset 131 3db97cf7f2c7
parent 112 4d9895fb15bc
child 135 a7ccde9d9eed
equal deleted inserted replaced
130:63248d9db484 131:3db97cf7f2c7
    20 __authors__ = [
    20 __authors__ = [
    21   '"Todd Larsen" <tlarsen@google.com>',
    21   '"Todd Larsen" <tlarsen@google.com>',
    22   ]
    22   ]
    23 
    23 
    24 
    24 
       
    25 import re
       
    26 
    25 from google.appengine.api import users
    27 from google.appengine.api import users
       
    28 from google.appengine.ext import db
    26 
    29 
    27 from soc.logic import out_of_band
    30 from soc.logic import out_of_band
    28 
    31 
    29 import soc.models.user
    32 import soc.models.user
       
    33 
       
    34 
       
    35 def getUserKeyNameFromId(id):
       
    36   """Return a Datastore key_name for a User derived from a Google Account.
       
    37   
       
    38   Args:
       
    39     id: a Google Account (users.User) object
       
    40   """
       
    41   if not id:
       
    42     return None
       
    43 
       
    44   return 'User:%s' % id.email()
    30 
    45 
    31 
    46 
    32 def getIdIfMissing(id):
    47 def getIdIfMissing(id):
    33   """Gets Google Account of logged-in user (possibly None) if id is false.
    48   """Gets Google Account of logged-in user (possibly None) if id is false.
    34   
    49   
    36   accepts an optional id argument from the caller (such as one looked up
    51   accepts an optional id argument from the caller (such as one looked up
    37   already by another view that decides to "forward" the request to this
    52   already by another view that decides to "forward" the request to this
    38   other view).
    53   other view).
    39 
    54 
    40   Args:
    55   Args:
    41     id: a Google Account object, or None
    56     id: a Google Account (users.User) object, or None
    42     
    57     
    43   Returns:
    58   Returns:
    44     If id is non-false, it is simply returned; otherwise, the Google Account
    59     If id is non-false, it is simply returned; otherwise, the Google Account
    45     of currently logged-in user is returned (which could be None if no user
    60     of currently logged-in user is returned (which could be None if no user
    46     is logged in).
    61     is logged in).
    49     # id not initialized, so check if a Google Account is currently logged in
    64     # id not initialized, so check if a Google Account is currently logged in
    50     id = users.get_current_user()
    65     id = users.get_current_user()
    51 
    66 
    52   return id
    67   return id
    53 
    68 
    54   
    69 
    55 def getUserFromId(id):
    70 def getUserFromId(id):
    56   """Returns User entity for a Google Account, or None if not found.  
    71   """Returns User entity for a Google Account, or None if not found.  
    57     
    72     
    58   Args:
    73   Args:
    59     id: a Google Account object
    74     id: a Google Account (users.User) object
    60   """
    75   """
    61   return soc.models.user.User.gql('WHERE id = :1', id).get()
    76   # first, attempt a lookup by User:id key name
    62 
    77   key_name = getUserKeyNameFromId(id)
    63 
    78   
       
    79   if key_name:
       
    80     user = soc.models.user.User.get_by_key_name(key_name)
       
    81   else:
       
    82     user = None
       
    83   
       
    84   if user:
       
    85     return user
       
    86 
       
    87   # email address may have changed, so query the id property
       
    88   user = soc.models.user.User.gql('WHERE id = :1', id).get()
       
    89 
       
    90   if user:
       
    91     return user
       
    92 
       
    93   # last chance: perhaps the User changed their email address at some point
       
    94   user = soc.models.user.User.gql('WHERE former_ids = :1', id).get()
       
    95 
       
    96   return user
       
    97 
       
    98   
    64 def getUserIfMissing(user, id):
    99 def getUserIfMissing(user, id):
    65   """Conditionally returns User entity for a Google Account.
   100   """Conditionally returns User entity for a Google Account.
    66   
   101   
    67   This function is used to look up the User entity corresponding to the
   102   This function is used to look up the User entity corresponding to the
    68   supplied Google Account *if* the user parameter is false (usually None).
   103   supplied Google Account *if* the user parameter is false (usually None).
    72   one looked up already by another view that decides to "forward" the
   107   one looked up already by another view that decides to "forward" the
    73   HTTP request to this other view).
   108   HTTP request to this other view).
    74 
   109 
    75   Args:
   110   Args:
    76     user: None (usually), or an existing User entity
   111     user: None (usually), or an existing User entity
    77     id: a Google Account object
   112     id: a Google Account (users.User) object
    78     
   113     
    79   Returns:
   114   Returns:
    80     * user (which may have already been None if passed in that way by the
   115     * user (which may have already been None if passed in that way by the
    81       caller) if id is false or user is non-false
   116       caller) if id is false or user is non-false
    82     * results of getUserFromId() if user is false and id is non-false
   117     * results of getUserFromId() if user is false and id is non-false
    98     return True
   133     return True
    99   else:
   134   else:
   100     return False
   135     return False
   101 
   136 
   102 
   137 
       
   138 def isIdDeveloper(id=None):
       
   139   """Returns True if Google Account is a Developer with special privileges.
       
   140   
       
   141   Args:
       
   142     id: a Google Account (users.User) object; if id is not supplied,
       
   143       the current logged-in user is checked using the App Engine Users API.
       
   144       THIS ARGUMENT IS CURRENTLY IGNORED AND ONLY THE CURRENTLY LOGGED-IN
       
   145       USER IS CHECKED!
       
   146 
       
   147   See the TODO in the code below...
       
   148   """
       
   149   if not id:
       
   150     return users.is_current_user_admin()
       
   151 
       
   152   # TODO(tlarsen): this Google App Engine function only checks the currently
       
   153   #   logged in user.  There needs to be another way to do this, such as a
       
   154   #   field in the User Model...
       
   155   return users.is_current_user_admin()
       
   156 
       
   157 
       
   158 LINKNAME_PATTERN = r'''(?x)
       
   159     ^
       
   160     [0-9a-z]   # start with ASCII digit or lowercase
       
   161     (
       
   162      [0-9a-z]  # additional ASCII digit or lowercase
       
   163      |         # -OR-
       
   164      _[0-9a-z] # underscore and ASCII digit or lowercase
       
   165     )*         # zero or more of OR group
       
   166     $
       
   167 '''
       
   168 
       
   169 LINKNAME_REGEX = re.compile(LINKNAME_PATTERN)
       
   170 
       
   171 def isLinkNameFormatValid(link_name):
       
   172   """Returns True if link_name is in a valid format.
       
   173   
       
   174   Args:
       
   175     link_name: link name used in URLs to identify user
       
   176   """
       
   177   if LINKNAME_REGEX.match(link_name):
       
   178     return True
       
   179   return False
       
   180 
       
   181 
   103 def getUserFromLinkName(link_name):
   182 def getUserFromLinkName(link_name):
   104   """Returns User entity for link_name or None if not found.
   183   """Returns User entity for link_name or None if not found.
   105     
   184     
   106   Args:
   185   Args:
   107     link_name: link name used in URLs to identify user
   186     link_name: link name used in URLs to identify user
   134     return link_name_user
   213     return link_name_user
   135 
   214 
   136   # else: a link name was supplied, but there is no User that has it
   215   # else: a link name was supplied, but there is no User that has it
   137   raise out_of_band.ErrorResponse(
   216   raise out_of_band.ErrorResponse(
   138       'There is no user with a "link name" of "%s".' % link_name, status=404)
   217       'There is no user with a "link name" of "%s".' % link_name, status=404)
       
   218 
       
   219 
       
   220 def doesLinkNameBelongToId(link_name, id=None):
       
   221   """Returns True if supplied link name belongs to supplied Google Account.
       
   222   
       
   223   Args:
       
   224     link_name: link name used in URLs to identify user
       
   225     id: a Google Account object; optional, current logged-in user will
       
   226       be used (or False will be returned if no user is logged in)
       
   227   """
       
   228   id = getIdIfMissing(id)
       
   229     
       
   230   if not id:
       
   231     # id not supplied and no Google Account logged in, so link name cannot
       
   232     # belong to an unspecified User
       
   233     return False
       
   234 
       
   235   user = getUserFromId(id)
       
   236 
       
   237   if not user:
       
   238     # no User corresponding to id Google Account, so no link name at all 
       
   239     return False
       
   240 
       
   241   if user.link_name != link_name:
       
   242     # User exists for id, but does not have this link name
       
   243     return False
       
   244 
       
   245   return True  # link_name does actually belong to this Google Account
       
   246 
       
   247 
       
   248 def updateOrCreateUserFromId(id, **user_properties):
       
   249   """Update existing User entity, or create new one with supplied properties.
       
   250 
       
   251   Args:
       
   252     id: a Google Account object
       
   253     **user_properties: keyword arguments that correspond to User entity
       
   254       properties and their values
       
   255       
       
   256   Returns:
       
   257     the User entity corresponding to the Google Account, with any supplied
       
   258     properties changed, or a new User entity now associated with the Google
       
   259     Account and with the supplied properties
       
   260   """
       
   261   # attempt to retrieve the existing User
       
   262   user = getUserFromId(id)
       
   263   
       
   264   if not user:
       
   265     # user did not exist, so create one in a transaction
       
   266     key_name = getUserKeyNameFromId(id)
       
   267     user = soc.models.user.User.get_or_insert(
       
   268       key_name, id=id, **user_properties)
       
   269 
       
   270   # there is no way to be sure if get_or_insert() returned a new User or
       
   271   # got an existing one due to a race, so update with user_properties anyway,
       
   272   # in a transaction
       
   273   return updateUserProperties(user, **user_properties)
       
   274 
       
   275 
       
   276 def updateUserProperties(user, **user_properties):
       
   277   """Update existing User entity using supplied User properties.
       
   278 
       
   279   Args:
       
   280     user: a User entity
       
   281     **user_properties: keyword arguments that correspond to User entity
       
   282       properties and their values
       
   283       
       
   284   Returns:
       
   285     the original User entity with any supplied properties changed 
       
   286   """
       
   287   def update():
       
   288     return _unsafeUpdateUserProperties(user, **user_properties)
       
   289 
       
   290   return db.run_in_transaction(update)
       
   291 
       
   292   
       
   293 def _unsafeUpdateUserProperties(user, **user_properties):
       
   294   """(see updateUserProperties)
       
   295   
       
   296   Like updateUserProperties(), but not run within a transaction. 
       
   297   """
       
   298   properties = user.properties()
       
   299   
       
   300   for prop in properties.values():
       
   301     if prop.name in user_properties:
       
   302       if prop.name == 'former_ids':
       
   303         # former_ids cannot be overwritten directly
       
   304         continue
       
   305 
       
   306       value = user_properties[prop.name]
       
   307 
       
   308       if prop.name == 'id':
       
   309         old_id = user.id
       
   310 
       
   311         if value != old_id:
       
   312           user.former_ids.append(old_id)
       
   313 
       
   314       prop.__set__(user, value)
       
   315         
       
   316   user.put()
       
   317   return user