def _users_query(self, q, limit=10): from simplifiedpermissionsadminplugin.simplifiedpermissions import SimplifiedPermissions if SimplifiedPermissions and self.env.is_enabled(SimplifiedPermissions): sp = SimplifiedPermissions(self.env) # Keep track of users that have already been found to prevent # yielding duplicates of users belonging to several groups yielded_sids = set() for group, data in sp.group_memberships().items(): for member in data['members']: if q in member.sid and member.sid not in yielded_sids: # if the 'never logged in' text changes, then update # plugins/open/autocompleteplugin/autocompleteplugin/htdocs/js/jquery.tracautocomplete.js yield {'sid': member.sid, 'name': member.get('name', member.sid), 'email': member.get('email','')} yielded_sids.add(member.sid) else: perm = PermissionSystem(self.env) users = [sid for sid, permission in perm.get_all_permissions() if sid not in set("anonymous", "authenticated", "admin")] for sid in sorted(set(users)): if q in sid: session = DetachedSession(self.env, sid) yield {'sid': sid, 'name': session.get('name',''), 'email': session.get('email','Never logged in')}
def create_session(self, authname_base, attributes): """Create a new authenticated session. (In trac, authenticated sessions are, essentially “user accounts”, so this creates a new account or “login” on the trac.) If possible, the session is created with an ``sid`` of ``authname_base``. If a session already exists with that ``sid``, then a suffix is added to make the ``sid`` unique. The attributes of the new session are initialized from the ``attributes`` argument, if any. The ``sid`` of the new session is returned. """ if not attributes: raise ValueError("Attributes required for new session") for suffix in self.uniquifier_suffixes(): authname = authname_base + suffix if self.permission_exists_for(authname): continue ds = DetachedSession(self.env, authname) # At least in 0.12.2, this means no session exists. is_new = ds.last_visit == 0 and len(ds) == 0 if is_new: break for key, value in attributes.items(): ds[key] = value or '' ds.save() return authname
def add_known_user(self, username, name, email): session = DetachedSession(self.env, username) if name is not None: session["name"] = name if email is not None: session["email"] = email session.save()
def test_find_session_by_identity_url(self, env, userdb): bare_id_token = {'iss': 'https://example.net', 'sub': '42'} id_token = {'iss': 'https://example.net', 'sub': '42', 'openid_id': 'https://example.org/foo'} ds = DetachedSession(env, 'bar') ds['openid_session_identity_url_data'] = 'https://example.org/foo' ds.save() assert userdb.find_session(bare_id_token) is None assert userdb.find_session(id_token) == 'bar' assert userdb.find_session(bare_id_token) == 'bar'
def test_session_set(self): """Verify that setting a variable in a session to the default value removes it from the session. """ with self.env.db_transaction as db: db("INSERT INTO session VALUES ('john', 1, 0)") db("INSERT INTO session_attribute VALUES ('john', 1, 'foo', 'bar')") session = DetachedSession(self.env, 'john') self.assertEqual('bar', session['foo']) # Setting the variable to the default value removes the variable with self.env.db_transaction as db: session.set('foo', 'default', 'default') session.save() self.assertEqual(0, self.env.db_query(""" SELECT COUNT(*) FROM session_attribute WHERE sid='john' AND name='foo' """)[0][0]) # Setting the variable to a value different from the default sets it with self.env.db_transaction as db: session.set('foo', 'something', 'default') session.save() self.assertEqual('something', self.env.db_query(""" SELECT value FROM session_attribute WHERE sid='john' AND name='foo' """)[0][0])
def test_session_set_email(self): """Setting session email invalidates known_users cache.""" session = DetachedSession(self.env, 'user') session['email'] = '*****@*****.**' self.assertEqual([], list(self.env.get_known_users())) session.save() email = None for sid, name, email in self.env.get_known_users(): if sid == 'user': break self.assertEqual(session['email'], email)
def test_session_set_name(self): """Setting session name invalidates known_users cache.""" session = DetachedSession(self.env, 'user') session['name'] = 'The User' self.assertEqual([], list(self.env.get_known_users())) session.save() name = None for sid, name, email in self.env.get_known_users(): if sid == 'user': break self.assertEqual(session['name'], name)
def test_session_set_name(self): """Setting session name invalidates known_users cache.""" sid = 'user' name = 'The User' session = DetachedSession(self.env, sid) session['name'] = name self.assertEqual([], list(self.env.get_known_users())) session.save() known_users = list(self.env.get_known_users()) self.assertEqual(1, len(known_users)) self.assertEqual(sid, known_users[0][0]) self.assertEqual(name, known_users[0][1]) self.assertIsNone(known_users[0][2])
def test_session_set_email(self): """Setting session email invalidates known_users cache.""" sid = 'user' email = '*****@*****.**' session = DetachedSession(self.env, sid) session['email'] = email self.assertEqual([], list(self.env.get_known_users())) session.save() known_users = list(self.env.get_known_users()) self.assertEqual(1, len(known_users)) self.assertEqual(sid, known_users[0][0]) self.assertIsNone(known_users[0][1]) self.assertEqual(email, known_users[0][2])
def test_create_new_anonymous_session(self): """Setting attribute on a new anonymous session doesn't invalidate known_users cache.""" sid = 'anonymous' session = DetachedSession(self.env, sid) session.authenticated = False self.assertEqual([], list(self.env.get_known_users())) # insert a session record without invalidating cache self.env.insert_users([('user', 'Name' '*****@*****.**', 1)]) session.save() self.assertEqual([], list(self.env.get_known_users())) self.env.invalidate_known_users_cache() self.assertEqual(1, len(list(self.env.get_known_users())))
def test_find_session_by_attr(self, env, helper): ds = DetachedSession(env, 'foo') ds['bar'] = 'baz' ds.save() ds = DetachedSession(env, 'wrong') ds['bar'] = 'not baz' ds.save() assert helper.find_session_by_attr('bar', 'baz') == ['foo']
def getSession(self, queueid): cursor = self.env.get_db_cnx().cursor() cursor.execute("SELECT status, owner FROM buildqueue WHERE id = %s", (queueid,)) if cursor.rowcount != 1: return False row = cursor.fetchone() if row[0] != 90: return False if find(row[1], '@') != -1: return DetachedSession(self.env, "qatbot") return DetachedSession(self.env, row[1])
def _create_request(self, authname='anonymous', **kwargs): kw = { 'perm': PermissionCache(self.env, authname), 'args': {}, 'callbacks': {}, 'path_info': '', 'form_token': None, 'href': self.env.href, 'abs_href': self.env.abs_href, 'tz': utc, 'locale': None, 'lc_time': None, 'session': DetachedSession(self.env, authname), 'authname': authname, 'chrome': { 'notices': [], 'warnings': [] }, 'method': None, 'get_header': lambda v: None, 'is_xhr': False } kw.update(kwargs) def send(self, content, content_type='text/html', status=200): raise RequestDone return Mock(send=send, **kw)
def _create_request(self, authname='anonymous', **kwargs): kw = { 'path_info': '/', 'perm': MockPerm(), 'args': _RequestArgs(), 'href': self.env.href, 'abs_href': self.env.abs_href, 'tz': utc, 'locale': None, 'lc_time': locale_en, 'session': DetachedSession(self.env, authname), 'authname': authname, 'chrome': { 'notices': [], 'warnings': [] }, 'method': None, 'get_header': lambda v: None, 'is_xhr': False, 'form_token': None } if 'args' in kwargs: kw['args'].update(kwargs.pop('args')) kw.update(kwargs) def redirect(url, permanent=False): raise RequestDone return Mock(add_redirect_listener=lambda x: [].append(x), redirect=redirect, **kw)
def test_delete_detached_session_var(self): """ Verify that removing a variable in a session not associated with a request deletes the variable from the database. """ cursor = self.db.cursor() cursor.execute("INSERT INTO session VALUES ('john', 1, 0)") cursor.execute("INSERT INTO session_attribute VALUES " "('john', 1, 'foo', 'bar')") session = DetachedSession(self.env, 'john') self.assertEqual('bar', session['foo']) del session['foo'] session.save() cursor.execute("SELECT COUNT(*) FROM session_attribute " "WHERE sid='john' AND name='foo'") self.assertEqual(0, cursor.fetchone()[0])
def _get_ticket_data(self, req, results): ats = AgileToolsSystem(self.env) loc = LogicaOrderController(self.env) closed_statuses = loc.type_and_statuses_for_closed_statusgroups() # TODO calculate which statuses are closed using the query system # when it is able to handle this tickets = [] for result in results: if result['status'] not in closed_statuses[result['type']]: filtered_result = dict((k, v) for k, v in result.iteritems() if k in self.fields) if "remaininghours" in filtered_result: try: hours = float(filtered_result["remaininghours"]) except (ValueError, TypeError): hours = 0 del filtered_result["remaininghours"] else: hours = 0 if "effort" in filtered_result: try: storypoints = float(filtered_result['effort']) except (ValueError, TypeError): storypoints = 0 else: storypoints = 0 reporter = filtered_result["reporter"] session = DetachedSession(self.env, reporter) filtered_result.update({ 'id': result['id'], 'position': ats.position(result['id']), 'hours': hours, 'effort': storypoints, 'reporter': session.get('name', reporter), 'changetime': to_utimestamp(filtered_result['changetime']) }) tickets.append(filtered_result) return tickets
def test_modify_detached_session(self): """ Verify that modifying a variable in a session not associated with a request updates the database accordingly. """ cursor = self.db.cursor() cursor.execute("INSERT INTO session VALUES ('john', 1, 0)") cursor.execute("INSERT INTO session_attribute VALUES " "('john', 1, 'foo', 'bar')") session = DetachedSession(self.env, 'john') self.assertEqual('bar', session['foo']) session['foo'] = 'baz' session.save() cursor.execute("SELECT value FROM session_attribute " "WHERE sid='john' AND name='foo'") self.assertEqual('baz', cursor.fetchone()[0])
def test_modify_detached_session(self): """ Verify that modifying a variable in a session not associated with a request updates the database accordingly. """ with self.env.db_transaction as db: db("INSERT INTO session VALUES ('john', 1, 0)") db("INSERT INTO session_attribute VALUES ('john', 1, 'foo', 'bar')") session = DetachedSession(self.env, 'john') self.assertEqual('bar', session['foo']) session['foo'] = 'baz' session.save() self.assertEqual('baz', self.env.db_query(""" SELECT value FROM session_attribute WHERE sid='john' AND name='foo' """)[0][0])
def test_delete_detached_session_var(self): """ Verify that removing a variable in a session not associated with a request deletes the variable from the database. """ with self.env.db_transaction as db: db("INSERT INTO session VALUES ('john', 1, 0)") db("INSERT INTO session_attribute VALUES ('john', 1, 'foo', 'bar')") session = DetachedSession(self.env, 'john') self.assertEqual('bar', session['foo']) del session['foo'] session.save() self.assertEqual(0, self.env.db_query(""" SELECT COUNT(*) FROM session_attribute WHERE sid='john' AND name='foo' """)[0][0])
def _get_address_info(self, authname): "TODO: check env.get_known_users" # First check if it's a #define user sess = DetachedSession(self.env, authname) address = sess.get('email') real_name = None if not address: # Otherwise check if it's a valid email address real_name, address = self.parse_address(authname) if not address: if not sess.get('email'): raise ValueError(_('User %(user)s has no email address set', user=authname)) else: raise ValueError(_('%(address)s is not a valid email address', address=address)) if not real_name: real_name = sess.get('name') return real_name, address
def test_get_main_page(self): req = Mock(path_info='/tags', args={}, authname='reader', perm=self.reader, href=self.href, method='GET', chrome=dict(static_hash='hashme!'), session=DetachedSession(self.env, 'reader'), locale='', tz='') template, data, content_type = self.tag_rh.process_request(req) self.assertEquals('tag_view.html', template) self.assertEquals(None, content_type) self.assertEquals([ 'checked_realms', 'mincount', 'page_title', 'tag_body', 'tag_query', 'tag_realms' ], sorted(data.keys()))
def get_permission_groups(self, username): ds = DetachedSession(self.env, username) return ds.get('openid.teams', '').split(',')
def _do_process(self, req): """Handle the redirect from the OpenID server. """ db = self.env.get_db_cnx() oidconsumer, session = self._get_consumer(req, db) # Ask the library to check the response that the server sent # us. Status is a code indicating the response type. info is # either None or a string containing more information about # the return type. info = oidconsumer.complete(req.args, req.args["openid.return_to"]) css_class = "error" if info.status == consumer.FAILURE and info.identity_url: # In the case of failure, if info is non-None, it is the # URL that we were verifying. We include it in the error # message to help the user figure out what happened. fmt = "Verification of %s failed: %s" message = fmt % (cgi.escape(info.identity_url), info.message) elif info.status == consumer.SUCCESS: # Success means that the transaction completed without # error. If info is None, it means that the user cancelled # the verification. css_class = "alert" # This is a successful verification attempt. If this # was a real application, we would do our login, # comment posting, etc. here. fmt = "You have successfully verified %s as your identity." message = fmt % (cgi.escape(info.identity_url),) remote_user = info.identity_url reg_info = None ax_response = ax.FetchResponse.fromSuccessResponse(info) if ax_response: ax_data = ax_response.getExtensionArgs() email = ax_data.get("value.ext0.1", "") if email: reg_info = {"email": email, "fullname": email.split("@", 1)[0].replace(".", " ").title()} if not reg_info: response = sreg.SRegResponse.fromSuccessResponse(info) if response: reg_info = response.getExtensionArgs() if self.strip_protocol: remote_user = remote_user[remote_user.find("://") + 3 :] if self.strip_trailing_slash and remote_user[-1] == "/": remote_user = remote_user[:-1] if info.endpoint.canonicalID: # You should authorize i-name users by their canonicalID, # rather than their more human-friendly identifiers. That # way their account with you is not compromised if their # i-name registration expires and is bought by someone else. message += " This is an i-name, and its persistent ID is %s" % (cgi.escape(info.endpoint.canonicalID),) remote_user = info.endpoint.canonicalID allowed = True if self.re_white_list: self.env.log.debug("Filtering REMOTE_USER '%s' through white-list." % remote_user) allowed = False for item in self.re_white_list: if not allowed and item.match(remote_user): allowed = True self.env.log.debug("User white-listed.") if allowed and self.re_black_list: self.env.log.debug("Filtering REMOTE_USER '%s' through black-list." % remote_user) for item in self.re_black_list: if item.match(remote_user): allowed = False self.env.log.debug("User black-listed.") if allowed and self.check_list: params = {self.check_list_key: remote_user} if reg_info and reg_info.has_key("email") and len(reg_info["email"]) > 0: params["email"] = reg_info["email"] url = self.check_list + "?" + urllib.urlencode(params) self.env.log.debug("OpenID check list URL: %s" % url) result = simplejson.load(urllib.urlopen(url)) if not result[self.check_list_key]: allowed = False elif self.check_list_username: new_user = result[self.check_list_username] if new_user: remote_user = new_user if allowed: cookie = hex_entropy() req.outcookie["trac_auth"] = cookie req.outcookie["trac_auth"]["path"] = req.href() req.outcookie["trac_auth"]["expires"] = self.trac_auth_expires req.session[self.openid_session_identity_url_key] = info.identity_url if reg_info and reg_info.has_key("fullname") and len(reg_info["fullname"]) > 0: req.session["name"] = reg_info["fullname"] if reg_info and reg_info.has_key("email") and len(reg_info["email"]) > 0: req.session["email"] = reg_info["email"] self._commit_session(session, req) if self.combined_username and req.session["name"]: remote_user = "******" % (req.session["name"], remote_user) else: if req.session.has_key("name"): remote_user = req.session["name"] # Check if we generated a colliding remote_user and make the user unique collisions = 0 cremote_user = remote_user while True: ds = DetachedSession(self.env, remote_user) if not ds.last_visit: # New session break if not ds.has_key(self.openid_session_identity_url_key): # Old session, without the identity url set # Save the identity url then (bascially adopt the session) ds[self.openid_session_identity_url_key] = info.identity_url ds.save() break if ds[self.openid_session_identity_url_key] == info.identity_url: # No collision break # We got us a collision # Make the thing unique collisions += 1 remote_user = "******" % (cremote_user, collisions + 1) req.authname = remote_user db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute( "INSERT INTO auth_cookie (cookie,name,ipnr,time) " "VALUES (%s, %s, %s, %s)", (cookie, remote_user, self._get_masked_address(req.remote_addr), int(time.time())), ) db.commit() req.redirect(req.session.get("oid.referer") or self.env.abs_href()) else: message = "You are not allowed here." elif info.status == consumer.CANCEL: # cancelled message = "Verification cancelled" elif info.status == consumer.SETUP_NEEDED: if info.setup_url: message = "<a href=%s>Setup needed</a>" % (quoteattr(info.setup_url),) else: # This means auth didn't succeed, but you're welcome to try # non-immediate mode. message = "Setup needed" else: # Either we don't understand the code or there is no # openid_url included with the error. Give a generic # failure message. The library should supply debug # information in a log. message = "Verification failed." self._commit_session(session, req) add_stylesheet(req, "authopenid/css/openid.css") add_script(req, "authopenid/js/openid-jquery.js") return ( "openidlogin.html", { "images": req.href.chrome("authopenid/images") + "/", "action": req.href.openidverify(), "message": message, "signup": self.signup_link, "whatis": self.whatis_link, "css_class": css_class, "custom_provider_name": self.custom_provider_name, "custom_provider_label": self.custom_provider_label, "custom_provider_url": self.custom_provider_url, "custom_provider_image": self.custom_provider_image, }, None, )
def _do_process(self, req): """Handle the redirect from the OpenID server. """ db = self.env.get_db_cnx() oidconsumer, oidsession = self._get_consumer(req, db) # Ask the library to check the response that the server sent # us. Status is a code indicating the response type. info is # either None or a string containing more information about # the return type. current_url = req.abs_href(req.path_info) info = oidconsumer.complete(req.args,current_url) css_class = 'error' if info.status == consumer.FAILURE and info.identity_url: # In the case of failure, if info is non-None, it is the # URL that we were verifying. We include it in the error # message to help the user figure out what happened. fmt = "Verification of %s failed: %s" message = fmt % (cgi.escape(info.identity_url), info.message) elif info.status == consumer.SUCCESS: # Success means that the transaction completed without # error. If info is None, it means that the user cancelled # the verification. css_class = 'alert' session_attr = {} # attributes for new "user" # This is a successful verification attempt. If this # was a real application, we would do our login, # comment posting, etc. here. fmt = "You have successfully verified %s as your identity." message = fmt % (cgi.escape(info.identity_url),) remote_user = info.identity_url sreg_info = sreg.SRegResponse.fromSuccessResponse(info) or {} ax_response = ax.FetchResponse.fromSuccessResponse(info) ax_info = {} if ax_response: for alias, uri in self.openid_ax_attrs.items(): values = ax_response.data.get(uri,[]) if values: ax_info[alias] = values[0] email = (ax_info.get('email') or ax_info.get('email2') or sreg_info.get('email')) fullname = ( ' '.join(filter(None, map(ax_info.get, ('firstname', 'lastname')))) or sreg_info.get('fullname') or (email and email.split('@',1)[0].replace('.', ' ').title())) nickname = sreg_info.get('nickname') if self.groups_to_request and TeamsResponse: teams_response = TeamsResponse.fromSuccessResponse(info) if teams_response: # be careful not to make user a member of any trac groups # not named in groups_to_request teams = set(teams_response.teams ).intersection(self.groups_to_request) if teams: session_attr['openid.teams'] = ','.join(teams) if self.strip_protocol: remote_user = remote_user[remote_user.find('://')+3:] if self.strip_trailing_slash and remote_user[-1] == '/': remote_user = remote_user[:-1] if info.endpoint.canonicalID: # You should authorize i-name users by their canonicalID, # rather than their more human-friendly identifiers. That # way their account with you is not compromised if their # i-name registration expires and is bought by someone else. message += (" This is an i-name, and its persistent ID is %s" % (cgi.escape(info.endpoint.canonicalID),)) remote_user = info.endpoint.canonicalID allowed = True if self.re_white_list: self.env.log.debug("Filtering REMOTE_USER '%s' through white-list." % remote_user) allowed = False for item in self.re_white_list: if not allowed and item.match(remote_user): allowed = True self.env.log.debug("User white-listed.") if allowed and self.re_black_list: self.env.log.debug("Filtering REMOTE_USER '%s' through black-list." % remote_user) for item in self.re_black_list: if item.match(remote_user): allowed = False self.env.log.debug("User black-listed.") if allowed and self.re_email_white_list: self.env.log.debug("Filtering email %r through email white-list." % email) allowed = False if email: for item in self.re_email_white_list: if not allowed and item.match(email): allowed = True self.env.log.debug("User email white-listed.") if allowed and self.check_list: allowed = False params = {self.check_list_key: remote_user} if email: params['email'] = email url = self.check_list + '?' + urllib.urlencode(params) self.env.log.debug('OpenID check list URL: %s' % url) try: result = json.load(urllib.urlopen(url)) if result[self.check_list_key]: if self.check_list_username: cl_username = unicode( result[self.check_list_username]) if not cl_username: raise ValueError("Bad value for username") allowed = True except Exception, ex: self.env.log.error('OpenID check_list failed: %s' % ex) if allowed: cookie = hex_entropy() cookie_lifetime = self.trac_auth_cookie_lifetime req.outcookie['trac_auth'] = cookie req.outcookie['trac_auth']['path'] = req.href() if cookie_lifetime > 0: req.outcookie['trac_auth']['expires'] = cookie_lifetime session_attr[self.openid_session_identity_url_key] = info.identity_url if email: session_attr['email'] = email if fullname: session_attr['name'] = fullname self._commit_oidsession(oidsession, req) # First look for an existing authenticated session with # matching identity_url. self.env.log.debug('Checking URL: %s' % info.identity_url) authname_for_identity_url = self.get_user(info.identity_url) if authname_for_identity_url: authname = authname_for_identity_url ds = DetachedSession(self.env, authname) # The user already exists, update team membership # XXX: Should also update name and/or email? (This would # be an API change.) for name in ['openid.teams']: if name in session_attr: ds[name] = session_attr[name] elif name in ds: del ds[name] ds.save() else: # New identity URL -> create new authname/user. if self.check_list and self.check_list_username: authname = cl_username elif self.use_email_as_authname and email: authname = email elif self.use_nickname_as_authname and nickname: authname = nickname elif session_attr.get('name'): authname = session_attr['name'] if self.combined_username: authname = '%s <%s>' % (authname, remote_user) else: authname = remote_user # Possibly lower-case the authname. if self.lowercase_authname: authname = authname.lower() if self.trust_authname: ds = DetachedSession(self.env, authname) else: # Make authname unique in case of collisions def authnames(base): yield base for attempt in itertools.count(2): yield "%s (%d)" % (base, attempt) users_and_groups_with_permissions = set( user for user, perm in PermissionSystem(self.env).get_all_permissions()) for authname in authnames(authname): ds = DetachedSession(self.env, authname) # At least in 0.12.2, this means no session exists. no_session_exists = ds.last_visit == 0 and len(ds) == 0 no_permissions_defined = authname not in users_and_groups_with_permissions if (no_session_exists and no_permissions_defined): # name is free :-) break # Set attributes for new user on the # current anonymous session. It will be promoted to # the new authenticated session on the next request # (by Session.__init__). # # NB: avoid dict.update here to ensure that # DetachedSession.__getitem__ gets a chance to # normalize values for name, value in session_attr.items(): req.session[name] = value self.env.log.info("Created new user '%s' for " "OpenID identifier %s", authname, info.identity_url) req.authname = authname db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("INSERT INTO auth_cookie (cookie,name,ipnr,time) " "VALUES (%s, %s, %s, %s)", (cookie, authname, self._get_masked_address(req.remote_addr), int(time.time()))) db.commit() req.redirect(req.session.get('oid.referer') or self.env.abs_href()) else: message = 'You are not allowed here.'
def make_session(sid, last_visit): monkeypatch.setattr("time.time", lambda: last_visit) ds = DetachedSession(env, sid) ds['bar'] = 'baz' ds.save()
def test_find_session(self, env, userdb): ds = DetachedSession(env, 'foo') ds['trac_oidc.subject'] = 'https://example.net?sub=42' ds.save() sid = userdb.find_session({'iss': 'https://example.net', 'sub': '42'}) assert sid == 'foo'
def _do_process(self, req): """Handle the redirect from the OpenID server. """ db = self.env.get_db_cnx() oidconsumer, oidsession = self._get_consumer(req, db) # Ask the library to check the response that the server sent # us. Status is a code indicating the response type. info is # either None or a string containing more information about # the return type. info = oidconsumer.complete(req.args, req.args['openid.return_to']) css_class = 'error' if info.status == consumer.FAILURE and info.identity_url: # In the case of failure, if info is non-None, it is the # URL that we were verifying. We include it in the error # message to help the user figure out what happened. fmt = "Verification of %s failed: %s" message = fmt % (cgi.escape(info.identity_url), info.message) elif info.status == consumer.SUCCESS: # Success means that the transaction completed without # error. If info is None, it means that the user cancelled # the verification. css_class = 'alert' session_attr = {} # attributes for new "user" # This is a successful verification attempt. If this # was a real application, we would do our login, # comment posting, etc. here. fmt = "You have successfully verified %s as your identity." message = fmt % (cgi.escape(info.identity_url), ) remote_user = info.identity_url sreg_info = sreg.SRegResponse.fromSuccessResponse(info) or {} ax_response = ax.FetchResponse.fromSuccessResponse(info) ax_info = {} if ax_response: for alias, uri in self.openid_ax_attrs.items(): values = ax_response.data.get(uri, []) if values: ax_info[alias] = values[0] email = (ax_info.get('email') or ax_info.get('email2') or sreg_info.get('email')) fullname = (' '.join( filter(None, map(ax_info.get, ('firstname', 'lastname')))) or sreg_info.get('fullname') or (email and email.split('@', 1)[0].replace('.', ' ').title())) nickname = sreg_info.get('nickname') if self.groups_to_request and TeamsResponse: teams_response = TeamsResponse.fromSuccessResponse(info) if teams_response: # be careful not to make user a member of any trac groups # not named in groups_to_request teams = set(teams_response.teams).intersection( self.groups_to_request) if teams: session_attr['openid.teams'] = ','.join(teams) if self.strip_protocol: remote_user = remote_user[remote_user.find('://') + 3:] if self.strip_trailing_slash and remote_user[-1] == '/': remote_user = remote_user[:-1] if info.endpoint.canonicalID: # You should authorize i-name users by their canonicalID, # rather than their more human-friendly identifiers. That # way their account with you is not compromised if their # i-name registration expires and is bought by someone else. message += ( " This is an i-name, and its persistent ID is %s" % (cgi.escape(info.endpoint.canonicalID), )) remote_user = info.endpoint.canonicalID allowed = True if self.re_white_list: self.env.log.debug( "Filtering REMOTE_USER '%s' through white-list." % remote_user) allowed = False for item in self.re_white_list: if not allowed and item.match(remote_user): allowed = True self.env.log.debug("User white-listed.") if allowed and self.re_black_list: self.env.log.debug( "Filtering REMOTE_USER '%s' through black-list." % remote_user) for item in self.re_black_list: if item.match(remote_user): allowed = False self.env.log.debug("User black-listed.") if allowed and self.re_email_white_list: self.env.log.debug( "Filtering email %r through email white-list." % email) allowed = False if email: for item in self.re_email_white_list: if not allowed and item.match(email): allowed = True self.env.log.debug("User email white-listed.") if allowed and self.check_list: allowed = False params = {self.check_list_key: remote_user} if email: params['email'] = email url = self.check_list + '?' + urllib.urlencode(params) self.env.log.debug('OpenID check list URL: %s' % url) try: result = json.load(urllib.urlopen(url)) if result[self.check_list_key]: if self.check_list_username: cl_username = unicode( result[self.check_list_username]) if not cl_username: raise ValueError("Bad value for username") allowed = True except Exception, ex: self.env.log.error('OpenID check_list failed: %s' % ex) if allowed: cookie = hex_entropy() cookie_lifetime = self.trac_auth_cookie_lifetime req.outcookie['trac_auth'] = cookie req.outcookie['trac_auth']['path'] = req.href() if cookie_lifetime > 0: req.outcookie['trac_auth']['expires'] = cookie_lifetime session_attr[ self.openid_session_identity_url_key] = info.identity_url if email: session_attr['email'] = email if fullname: session_attr['name'] = fullname self._commit_oidsession(oidsession, req) if self.check_list and self.check_list_username: authname = cl_username elif self.use_nickname_as_authname and nickname: authname = nickname elif session_attr.get('name'): authname = session_attr['name'] if self.combined_username: authname = '%s <%s>' % (authname, remote_user) else: authname = remote_user # Possibly lower-case the authname. if self.lowercase_authname: authname = authname.lower() if self.trust_authname: ds = DetachedSession(self.env, authname) else: # Make authname unique in case of collisions # # XXX: We ought to first look for an existing authenticated # session with matching identity_url, and just use that # for the authid. (E.g. what if the user changes his # fullname at the openid provider?) However, trac does # not seem to provide an API for searching sessions other # than by sid/authname. # def authnames(base): yield base for attempt in itertools.count(2): yield "%s (%d)" % (base, attempt) existing_users_and_groups = set( user for user, perm in PermissionSystem( self.env).get_all_permissions()) for authname in authnames(authname): ds = DetachedSession(self.env, authname) if ds.last_visit == 0 and len(ds) == 0: # At least in 0.12.2, this mean no session exists. if authname in existing_users_and_groups: # Permissions are already defined for this user continue break ds_identity = ds.get( self.openid_session_identity_url_key) if ds_identity == info.identity_url: # No collision break if ds and (ds.last_visit != 0 or len(ds) > 0): # The user already exists, update team membership # XXX: Should also update name and/or email? (This would # be an API change.) for name in ['openid.teams']: if name in session_attr: ds[name] = session_attr[name] elif name in ds: del ds[name] ds.save() else: # We are creating a new "user". Set attributes on the # current anonymous session. It will be promoted to # the new authenticated session on the next request # (by Session.__init__). # # NB: avoid dict.update here to ensure that # DetachedSession.__getitem__ gets a chance to # normalize values for name, value in session_attr.items(): req.session[name] = value req.authname = authname db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute( "INSERT INTO auth_cookie (cookie,name,ipnr,time) " "VALUES (%s, %s, %s, %s)", (cookie, authname, self._get_masked_address( req.remote_addr), int(time.time()))) db.commit() req.redirect( req.session.get('oid.referer') or self.env.abs_href()) else: message = 'You are not allowed here.'
def setUp(self): self.env = EnvironmentStub( default_data=True, enable=[ default_workflow.ConfigurableTicketWorkflow, DefaultPermissionPolicy, DefaultPermissionStore, BatchModifyModule, api.TicketSystem, web_ui.TicketModule ]) self.env.config.set('trac', 'permission_policies', 'DefaultPermissionPolicy') ps = PermissionSystem(self.env) ps.grant_permission('has_ta_&_bm', 'TICKET_ADMIN') ps.grant_permission('has_bm', 'TICKET_BATCH_MODIFY') ps.grant_permission('has_ta_&_bm', 'TICKET_BATCH_MODIFY') session = DetachedSession(self.env, 'has_ta_&_bm') session.set('query_href', '') session.save() session = DetachedSession(self.env, 'has_bm') session.set('query_href', '') session.save()
def associate_session(self, authname, iss, sub): ds = DetachedSession(self.env, authname) ds[self.SUBJECT_SKEY] = self.subject_uri(iss, sub) ds.save()
def _add_session(self, sid, **attrs): session = DetachedSession(self.env, sid) for name, value in attrs.iteritems(): session[name] = value session.save()
def _do_process(self, req): """Handle the redirect from the OpenID server. """ db = self.env.get_db_cnx() oidconsumer, session = self._get_consumer(req, db) # Ask the library to check the response that the server sent # us. Status is a code indicating the response type. info is # either None or a string containing more information about # the return type. info = oidconsumer.complete(req.args,req.args['openid.return_to']) css_class = 'error' if info.status == consumer.FAILURE and info.identity_url: # In the case of failure, if info is non-None, it is the # URL that we were verifying. We include it in the error # message to help the user figure out what happened. fmt = "Verification of %s failed: %s" message = fmt % (cgi.escape(info.identity_url), info.message) elif info.status == consumer.SUCCESS: # Success means that the transaction completed without # error. If info is None, it means that the user cancelled # the verification. css_class = 'alert' # This is a successful verification attempt. If this # was a real application, we would do our login, # comment posting, etc. here. fmt = "You have successfully verified %s as your identity." message = fmt % (cgi.escape(info.identity_url),) remote_user = info.identity_url sreg_info = sreg.SRegResponse.fromSuccessResponse(info) or {} ax_response = ax.FetchResponse.fromSuccessResponse(info) ax_info = {} if ax_response: for alias, uri in self.openid_ax_attrs.items(): values = ax_response.data.get(uri,[]) if values: ax_info[alias] = values[0] email = (ax_info.get('email') or ax_info.get('email2') or sreg_info.get('email')) fullname = (' '.join(filter(None, map(ax_info.get, ('firstname', 'lastname')))) or sreg_info.get('fullname') or email.split('@',1)[0].replace('.', ' ').title()) if self.strip_protocol: remote_user = remote_user[remote_user.find('://')+3:] if self.strip_trailing_slash and remote_user[-1] == '/': remote_user = remote_user[:-1] if info.endpoint.canonicalID: # You should authorize i-name users by their canonicalID, # rather than their more human-friendly identifiers. That # way their account with you is not compromised if their # i-name registration expires and is bought by someone else. message += (" This is an i-name, and its persistent ID is %s" % (cgi.escape(info.endpoint.canonicalID),)) remote_user = info.endpoint.canonicalID allowed = True if self.re_white_list: self.env.log.debug("Filtering REMOTE_USER '%s' through white-list." % remote_user) allowed = False for item in self.re_white_list: if not allowed and item.match(remote_user): allowed = True self.env.log.debug("User white-listed.") if allowed and self.re_black_list: self.env.log.debug("Filtering REMOTE_USER '%s' through black-list." % remote_user) for item in self.re_black_list: if item.match(remote_user): allowed = False self.env.log.debug("User black-listed.") if allowed and email and self.re_email_white_list: self.env.log.debug("Filtering email '%s' through email white-list." % email) allowed = False for item in self.re_email_white_list: if not allowed and item.match(email): allowed = True self.env.log.debug("User email white-listed.") if allowed and self.check_list: params = {self.check_list_key: remote_user} if email: params['email'] = email url = self.check_list + '?' + urllib.urlencode(params) self.env.log.debug('OpenID check list URL: %s' % url) result = json.load(urllib.urlopen(url)) if not result[self.check_list_key]: allowed = False elif self.check_list_username: new_user = result[self.check_list_username] if new_user: remote_user = new_user if allowed: cookie = hex_entropy() cookie_lifetime = self.trac_auth_cookie_lifetime req.outcookie['trac_auth'] = cookie req.outcookie['trac_auth']['path'] = req.href() if cookie_lifetime > 0: req.outcookie['trac_auth']['expires'] = cookie_lifetime req.session[self.openid_session_identity_url_key] = info.identity_url if email: req.session['email'] = email if fullname: req.session['name'] = fullname self._commit_session(session, req) if req.session.get('name'): authname = req.session['name'] if self.combined_username: authname = '%s <%s>' % (authname, remote_user) # Possibly lower-case the authname. if self.lowercase_authname: authname = authname.lower() # Make authname unique in case of collisions # # XXX: We ought to first look for an existing authenticated # ssession with matching identity_url, and just use that # for the authid. (E.g. what if the user changes his # fullname at the openid provider?) However, trac does # not seem to provide an API for searching sessions other # than by sid/authname. # def authnames(base): yield base for attempt in itertools.count(2): yield "%s (%d)" % (base, attempt) for authname in authnames(authname): ds = DetachedSession(self.env, authname) if ds.last_visit == 0 and len(ds) == 0: # At least in 0.12.2, this mean no session exists. break ds_identity = ds.get(self.openid_session_identity_url_key) if ds_identity == info.identity_url: # No collision break req.authname = authname db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("INSERT INTO auth_cookie (cookie,name,ipnr,time) " "VALUES (%s, %s, %s, %s)", (cookie, authname, self._get_masked_address(req.remote_addr), int(time.time()))) db.commit() req.redirect(req.session.get('oid.referer') or self.env.abs_href()) else: message = 'You are not allowed here.' elif info.status == consumer.CANCEL: # cancelled message = 'Verification cancelled' elif info.status == consumer.SETUP_NEEDED: if info.setup_url: message = '<a href=%s>Setup needed</a>' % ( quoteattr(info.setup_url),) else: # This means auth didn't succeed, but you're welcome to try # non-immediate mode. message = 'Setup needed' else: # Either we don't understand the code or there is no # openid_url included with the error. Give a generic # failure message. The library should supply debug # information in a log. message = 'Verification failed.' self._commit_session(session, req) add_stylesheet(req, 'authopenid/css/openid.css') add_script(req, 'authopenid/js/openid-jquery.js') return 'openidlogin.html', { 'images': req.href.chrome('authopenid/images') + '/', 'action': req.href.openidverify(), 'message': message, 'signup': self.signup_link, 'whatis': self.whatis_link, 'css_class': css_class, 'providers_regexp': self.providers_regexp, 'custom_provider_name': self.custom_provider_name, 'custom_provider_label': self.custom_provider_label, 'custom_provider_url': self.custom_provider_url, 'custom_provider_image': self.custom_provider_image, 'custom_provider_size': self.custom_provider_size, }, None
def test_create_session(self, env, helper): sid = helper.create_session('foo', {'name': 'Joe'}) assert sid == 'foo' ds = DetachedSession(env, 'foo') assert ds['name'] == 'Joe'
def test_associate_session(self, env, userdb): userdb.associate_session('foo', 'https://example.net', '42') ds = DetachedSession(env, 'foo') assert ds[userdb.SUBJECT_SKEY] == 'https://example.net?sub=42'
def setUp(self): self.env = EnvironmentStub( default_data=True, enable=[ default_workflow.ConfigurableTicketWorkflow, DefaultPermissionPolicy, DefaultPermissionStore, BatchModifyModule, api.TicketSystem, web_ui.TicketModule ]) self.env.config.set('trac', 'permission_policies', 'DefaultPermissionPolicy') self.env.config.set('ticket-custom', 'text1', 'text') self.env.config.set('ticket-custom', 'text1.max_size', 5) self.env.config.set('ticket-custom', 'time1', 'time') self.env.config.set('ticket-custom', 'time1.format', 'date') self.env.config.set('ticket-workflow', 'acknowledge', '* -> acknowledged') ps = PermissionSystem(self.env) ps.grant_permission('has_ta_&_bm', 'TICKET_ADMIN') ps.grant_permission('has_bm', 'TICKET_BATCH_MODIFY') ps.grant_permission('has_ta_&_bm', 'TICKET_BATCH_MODIFY') session = DetachedSession(self.env, 'has_ta_&_bm') session.set('query_href', '') session.save() session = DetachedSession(self.env, 'has_bm') session.set('query_href', '') session.save() self._insert_ticket('Ticket 1', reporter='user1', component='component1', description='the desc', keywords='foo one', status='new') self._insert_ticket('Ticket 2', reporter='user1', component='component2', description='the desc', keywords='baz two', status='new')