def _cache_set(self, key, value): """Set an item in the cache. It gets a default ttl.""" key = self.cache_prefix + key with self.cache_pool.reserve() as mc: try: if not mc.set(key, json.dumps(value), self.cache_timeout): raise BackendError('memcached error') except pylibmc.Error, err: raise BackendError(str(err))
def create_user(self, user_name, password, email): """Creates a user""" if not self.allow_new_users: raise BackendError("Creation of new users is disabled") user = User() if user_name in self._users: return False id_ = random.randint(1, 2000) ids = self._users.values() while id_ in ids: id_ = random.randint(1, 2000) self._users[user_name] = { 'userid': id_, 'password': password, 'mail': email, 'username': user_name, 'primaryNode': '' } user['username'] = user_name user['userid'] = id_ user['mail'] = email return user
def create_user(self, username, password, email, **extra_fields): """Creates a user. Returns True on success.""" if not self.allow_new_users: raise BackendError("Creation of new users is disabled") password_hash = sscrypt(password) values = { 'username': username, 'password': password_hash, 'mail': email, } for field in ('userid', 'accountStatus', 'mailVerified', 'syncNode'): if field in extra_fields: values[field] = extra_fields[field] query = insert(users).values(**values) try: res = safe_execute(self._engine, query) except IntegrityError: #Name already exists return False if res.rowcount != 1: return False #need a copy with some of the info for the return value userobj = User() userobj['username'] = username userobj['userid'] = res.lastrowid userobj['mail'] = email return userobj
def admin_update_password(self, user, new_password, key=None): """Change the user password. Uses the admin bind or the user bind if the old password is provided. Args: user_id: user id password: new password key: the reset code if needed for proxying Returns: True if the change was successful, False otherwise """ payload = {'reset_code': key, 'password': new_password} username = user.get('username') if username is None: return False url = self._generate_url(username, 'password') status, body = self._proxy('POST', url, payload) if status == 200: return body == 0 elif status == 400: if body == ERROR_INVALID_RESET_CODE: raise InvalidCodeError() msg = 'Unable to change the user password via sreg. ' msg += 'Received body:\n%s\n' % str(body) msg += 'Received status: %d' % status raise BackendError(msg, server=url)
def delete_user(self, user, credentials=None): """Deletes the user Args: user_id: user id credentials: user authentication credentials Returns: True if the change was successful, False otherwise """ if credentials is None: return False password = credentials.get("password") if password is None: return False payload = {'password': password} username = user.get('username') if username is None: return False url = self._generate_url(username) status, body = self._proxy('DELETE', url, payload) if status != 200: msg = 'Unable to delete the user via sreg. ' msg += 'Received body:\n%s\n' % str(body) msg += 'Received status: %d' % status raise BackendError(msg, server=url) return body == 0
def generate_reset_code(self, user, overwrite=False): """Returns the a reset code for user. Args: user: the user object. Must have a value for user[userid] overwrite: if set to False, returns the current key if already generated Returns: The reset code, or None if there's a problem. """ username = user.get('username', None) if not username: raise NoUserIDError() status, body = self._proxy( 'GET', self._generate_url(username, 'password_reset_code')) if status == 200 and body == 0: raise AlreadySentError() if status == 400: if body == ERROR_NO_EMAIL_ADDRESS: raise NoEmailError() raise BackendError()
def safe_execute(engine, *args, **kwargs): """Execution wrapper that will raise a HTTPServiceUnavailableError on any OperationalError errors and log it. """ try: # It's possible for the backend to raise a "connection invalided" error # if e.g. the server timed out the connection. SQLAlchemy purges the # the whole connection pool if this happens, so one retry is enough. try: return execute_with_cleanup(engine, *args, **kwargs) except DBAPIError, exc: if _is_retryable_db_error(engine, exc): logger = CLIENT_HOLDER.default_client logger.incr('services.util.safe_execute.retry') logger.debug('retrying due to db error %r', exc) return execute_with_cleanup(engine, *args, **kwargs) else: raise except Exception, exc: if not _is_operational_db_error(engine, exc): raise err = traceback.format_exc() logger = CLIENT_HOLDER.default_client logger.error(err) # Due to an apparent bug in SQLAlchemy, it's possible for some kinds # of connection failure to leave zombies checked out of the pool. # Ref: http://www.sqlalchemy.org/trac/ticket/2695 # We employ a rather brutal workaround: re-create the entire pool. if _get_mysql_error_code(engine, exc) in (2006, 2013, 2014): if not exc.connection_invalidated: engine.dispose() raise BackendError(str(exc))
def create_user(self, username, password, email): """Creates a user. Returns True on success.""" payload = {'password': password, 'email': email} url = self.generate_url(username) status, body = self._proxy('PUT', url, payload) if status != 200: raise BackendError() # the result is the username on success return body == username
def authenticate_user(self, user, credentials, attrs=None): password = credentials.get("password") if not password: return None username = user.get("username") if username is None: return None code, headers, body = get_url(self.whoami_uri, "GET", user=username, password=password) if code == 401: return None if code != 200: logger = CLIENT_HOLDER.default_client logger.error("whoami API unexpected behaviour") logger.error(" code: %r", code) logger.error(" headers: %r", headers) logger.error(" body: %r", body) raise BackendError("whoami API unexpected behaviour") try: user_data = json.loads(body) except ValueError: logger = CLIENT_HOLDER.default_client logger.error("whoami API produced invalid JSON") logger.error(" code: %r", code) logger.error(" headers: %r", headers) logger.error(" body: %r", body) raise BackendError("whoami API produced invalid JSON") user.update({ "userid": user_data["userid"], "username": username, "syncNode": user_data.get("syncNode", ""), }) return user["userid"]
def get_user_node(self, user_id, assign=True): if self.single_box: return None node = super(MozillaAuth, self).get_user_node(user_id, assign=False) if node is not None or assign is False: return node username = self._get_username(user_id) url = self.generate_url(username, 'node/weave') status, body = self._proxy('GET', url) if status != 200: raise BackendError() return body
def create_user(self, username, password, email): """Creates a user. Returns user on success, false otherwise.""" if not self.allow_new_users: raise BackendError("Creation of new users is disabled") payload = {'password': password, 'email': email} url = self._generate_url(username) status, body = self._proxy('PUT', url, payload) if status != 200: if body == ERROR_INVALID_WRITE: return False msg = 'Unable to create the user via sreg. ' msg += 'Received body:\n%s\n' % str(body) msg += 'Received status: %d' % status raise BackendError(msg, server=url) # the result is the username on success if body == username: user = User() user['username'] = username user['email'] = email return user else: return False
def _cache_get(self, *keys): """Get an item from the cache. If multiple arguments are given, attempt to load all those keys and return data from the first one successfully found. """ keys = [self.cache_prefix + key for key in keys] with self.cache_pool.reserve() as mc: try: values = mc.get_multi(keys) except pylibmc.Error, err: raise BackendError(str(err)) for key in keys: if key in values: return json.loads(values[key]) return None
def clear_reset_code(self, user_id): """Clears the reset code Args: user_id: user id Returns: True if the change was successful, False otherwise """ # handled by sreg username = self._get_username(user_id) status, body = self._proxy( 'DELETE', self.generate_url(username, 'password_reset_code')) if status != 200: raise BackendError() return body == 0
def generate_reset_code(self, user_id, overwrite=True): """Sends a reset code by e-mail Args: user_id: user id overwrite: if True, overwrites an existing code Returns: True if reset code was generated and sent to user, False otherwise """ username = self._get_username(user_id) status, body = self._proxy( 'GET', self.generate_url(username, 'password_reset_code')) if status == 200: return body == 0 if status == 400: if body == ERROR_NO_EMAIL_ADDRESS: raise NoEmailError() raise BackendError()
def delete_user(self, user_id, password=None): """Deletes the user Args: user_id: user id password: user password Returns: True if the change was successful, False otherwise """ if password is None: return False payload = {'password': password} username = self._get_username(user_id) url = self.generate_url(username) status, body = self._proxy('DELETE', url, payload) if status != 200: raise BackendError() return body == 0
def _set_reset_code(self, user_id): code = self._generate_reset_code() query = delete(reset_codes).where( and_(reset_codes.c.username == user_id, reset_codes.c.product == self.product)) self._engine.execute(query) expiration_time = datetime.datetime.now() + \ datetime.timedelta(seconds=self.expiration) query = insert(reset_codes).values(reset=code, expiration=expiration_time, product=self.product, username=user_id) res = safe_execute(self._engine, query) if res.rowcount != 1: raise BackendError('adding a reset code to the reset table failed') return code
def admin_update_password(self, user_id, new_password, key): """Change the user password. Uses the admin bind or the user bind if the old password is provided. Args: user_id: user id password: new password key: the reset code Returns: True if the change was successful, False otherwise """ payload = {'reset_code': key, 'password': new_password} username = self._get_username(user_id) url = self.generate_url(username, 'password') status, body = self._proxy('POST', url, payload) if status == 200: return body == 0 elif status == 400: if body == ERROR_INVALID_RESET_CODE: raise InvalidCodeError() raise BackendError()
def delete_user(self, user, credentials=None): raise BackendError("Disabled in ProxyUser")
def admin_update_password(self, user, new_password, code=None): raise BackendError("Disabled in ProxyUser")
def update_password(self, user, credentials, new_password): raise BackendError("Disabled in ProxyUser")
def admin_update_field(self, user, key, value): raise BackendError("Disabled in ProxyUser")
def update_field(self, user, credentials, key, value): raise BackendError("Disabled in ProxyUser")
def get_user_info(self, user, attrs=None): raise BackendError("Disabled in ProxyUser")
class ConnectionManager(object): """LDAP Connection Manager. Provides a context manager for LDAP connectors. """ def __init__(self, uri, bind=None, passwd=None, size=10, retry_max=3, retry_delay=.1, use_tls=False, single_box=False, timeout=-1, connector_cls=StateConnector, use_pool=False, max_lifetime=600, **kw): self._pool = [] self.size = size self.retry_max = retry_max self.retry_delay = retry_delay self.uri = uri self.bind = bind self.passwd = passwd self._pool_lock = RLock() self.use_tls = False self.timeout = timeout self.connector_cls = connector_cls self.use_pool = use_pool self.max_lifetime = max_lifetime def __len__(self): return len(self._pool) def _match(self, bind, passwd): if isinstance(passwd, unicode): passwd = passwd.encode('utf8') with self._pool_lock: inactives = [] for conn in reversed(self._pool): # already in usage if conn.active: continue # let's check the lifetime if conn.get_lifetime() > self.max_lifetime: # this connector has lived for too long, # we want to unbind it and remove it from the pool try: conn.unbind_s() except Exception: pass # XXX we will see later self._pool.remove(conn) continue # we found a connector for this bind if conn.who == bind and conn.cred == passwd: conn.active = True return conn inactives.append(conn) # no connector was available, let's rebind the latest inactive one if len(inactives) > 0: for conn in inactives: try: self._bind(conn, bind, passwd) return conn except Exception: self._pool.remove(conn) return None # There are no connector that match return None def _bind(self, conn, bind, passwd): # let's bind if self.use_tls: conn.start_tls_s() if bind is not None: conn.simple_bind_s(bind, passwd) conn.active = True def _create_connector(self, bind, passwd): """Creates a connector, binds it, and returns it Args: - bind: login - passwd: password """ tries = 0 connected = False exc = None if isinstance(passwd, unicode): passwd = passwd.encode('utf8') # trying retry_max times in a row with a fresh connector while tries < self.retry_max and not connected: try: conn = self.connector_cls(self.uri, retry_max=self.retry_max, retry_delay=self.retry_delay) conn.timeout = self.timeout self._bind(conn, bind, passwd) connected = True except ldap.LDAPError, exc: time.sleep(self.retry_delay) tries += 1 if not connected: # pass through logic errors directly. if isinstance(exc, (ldap.NO_SUCH_OBJECT, ldap.INVALID_CREDENTIALS, ldap.INVALID_DN_SYNTAX)): raise exc # operational errors become a BackendError. msg = str(exc) if isinstance(exc, ldap.TIMEOUT): raise BackendTimeoutError(msg, server=self.uri) else: raise BackendError(msg, backend=conn) return conn
def __call__(self, request): raise BackendError("meh", request=request)
def create_user(self, username, password, email): raise BackendError("Disabled in ProxyUser")
def _get_id(*args): raise BackendError()
def get_user_id(self, user): raise BackendError("Disabled in ProxyUser")