コード例 #1
0
 def __init__(self, timeout):
     self.debug = config.get('jsonfas.debug', False)
     if not self.fas:
         self.fas = FasProxyClient(self.fas_url, debug=self.debug,
                 session_name=config.get('visit.cookie.name', 'tg-visit'),
                 useragent='JsonFasVisitManager/%s' % __version__)
     BaseVisitManager.__init__(self, timeout)
     log.debug('JsonFasVisitManager.__init__: exit')
コード例 #2
0
 def _set_insecure(self, insecure):
     self._insecure = insecure
     self.proxy = FasProxyClient(self.base_url,
                                 useragent=self.useragent,
                                 session_as_cookie=False,
                                 debug=self.debug,
                                 insecure=insecure)
     return insecure
コード例 #3
0
 def __init__(self, timeout):
     self.log = log
     if not self.fas:
         JsonFasVisitManager.fas = FasProxyClient(
             self.fas_url,
             debug=self.debug,
             session_name=config.get('visit.cookie.name', 'tg-visit'),
             useragent='JsonFasVisitManager/%s' % __version__,
             retries=3)
     BaseVisitManager.__init__(self, timeout)
     self.log.debug('JsonFasVisitManager.__init__: exit')
コード例 #4
0
    def __init__(self, base_url='https://admin.rpmfusion.org/accounts/',
                 *args, **kwargs):
        '''Create the AccountSystem client object.

        :kwargs base_url: Base of every URL used to contact the server.
            Defaults to the Fedora Project FAS instance.
        :kwargs useragent: useragent string to use.  If not given, default to
            "Fedora Account System Client/VERSION"
        :kwargs debug: If True, log debug information
        :kwargs username: username for establishing authenticated connections
        :kwargs password: password to use with authenticated connections
        :kwargs session_cookie: **Deprecated** Use session_id instead.
            User's session_cookie to connect to the server
        :kwargs session_id: user's session_id to connect to the server
        :kwargs cache_session: if set to true, cache the user's session cookie
            on the filesystem between runs.
        '''
        if 'useragent' not in kwargs:
            kwargs['useragent'] = \
                'Fedora Account System Client/%s' % __version__

        super(AccountSystem, self).__init__(base_url, *args, **kwargs)
        # We need a single proxy for the class to verify username/passwords
        # against.
        if not self.proxy:
            self.proxy = FasProxyClient(base_url, useragent=self.useragent,
                                        session_as_cookie=False,
                                        debug=self.debug,
                                        insecure=self.insecure)

        # Preseed a list of FAS accounts with bugzilla addresses
        # This allows us to specify a different email for bugzilla than is
        # in the FAS db.  It is a hack, however, until FAS has a field for the
        # bugzilla address.
        self.__bugzilla_email = {
            # Nicolas Chauvet
            160404: '*****@*****.**',
        }
        # A few people have an email account that is used in owners.list but
        # have setup a bugzilla account for their primary account system email
        # address now.  Map these here.
        self.__alternate_email = {
            # Damien Durand: [email protected]
            '*****@*****.**': 100406,
            # Kevin Fenzi: [email protected]
            '*****@*****.**': 100037,
        }
        for bugzilla_map in self.__bugzilla_email.items():
            self.__alternate_email[bugzilla_map[1]] = bugzilla_map[0]
コード例 #5
0
 def _set_insecure(self, insecure):
     self._insecure = insecure
     self.proxy = FasProxyClient(self.base_url, useragent=self.useragent,
                                 session_as_cookie=False, debug=self.debug,
                                 insecure=insecure)
     return insecure
コード例 #6
0
    def __init__(self, base_url='https://admin.fedoraproject.org/accounts/',
                 *args, **kwargs):
        '''Create the AccountSystem client object.

        :kwargs base_url: Base of every URL used to contact the server.
            Defaults to the Fedora Project FAS instance.
        :kwargs useragent: useragent string to use.  If not given, default to
            "Fedora Account System Client/VERSION"
        :kwargs debug: If True, log debug information
        :kwargs username: username for establishing authenticated connections
        :kwargs password: password to use with authenticated connections
        :kwargs session_cookie: **Deprecated** Use session_id instead.
            User's session_cookie to connect to the server
        :kwargs session_id: user's session_id to connect to the server
        :kwargs cache_session: if set to true, cache the user's session cookie
            on the filesystem between runs.
        '''
        if 'useragent' not in kwargs:
            kwargs['useragent'] = \
                'Fedora Account System Client/%s' % __version__

        super(AccountSystem, self).__init__(base_url, *args, **kwargs)
        # We need a single proxy for the class to verify username/passwords
        # against.
        if not self.proxy:
            self.proxy = FasProxyClient(base_url, useragent=self.useragent,
                                        session_as_cookie=False,
                                        debug=self.debug,
                                        insecure=self.insecure)

        # Preseed a list of FAS accounts with bugzilla addresses
        # This allows us to specify a different email for bugzilla than is
        # in the FAS db.  It is a hack, however, until FAS has a field for the
        # bugzilla address.
        self.__bugzilla_email = {
            # Konstantin Ryabitsev: [email protected]
            100029: '*****@*****.**',
            # Sean Reifschneider: [email protected]
            100488: '*****@*****.**',
            # Karen Pease: [email protected]
            100281: '*****@*****.**',
            # Robert Scheck: [email protected]
            100093: '*****@*****.**',
            # Scott Bakers: [email protected]
            100881: '*****@*****.**',
            # Colin Charles: [email protected]
            100014: '*****@*****.**',
            # W. Michael Petullo: [email protected]
            100136: '*****@*****.**',
            # Elliot Lee: [email protected]
            100060: '*****@*****.**',
            # Control Center Team: Bugzilla user but email doesn't exist
            9908: '*****@*****.**',
            # Máirín Duffy
            100548: '*****@*****.**',
            # Muray McAllister: [email protected]
            102321: '*****@*****.**',
            # William Jon McCann: [email protected]
            102489: '*****@*****.**',
            # Matt Domsch's rebuild script -- bz email goes to /dev/null
            103590: '*****@*****.**',
            # Sindre Pedersen Bjørdal: [email protected]
            100460: '*****@*****.**',
            # Jesus M. Rodriguez: [email protected]
            102180: '*****@*****.**',
            # Roozbeh Pournader: [email protected]
            100350: '*****@*****.**',
            # Michael DeHaan: [email protected]
            100603: '*****@*****.**',
            # Sebastian Gosenheimer: [email protected]
            103647: '*****@*****.**',
            # Ben Konrath: [email protected]
            101156: '*****@*****.**',
            # Kai Engert: [email protected]
            100399: '*****@*****.**',
            # William Jon McCann: [email protected]
            102952: '*****@*****.**',
            # Simon Wesp: [email protected]
            109464: '*****@*****.**',
            # Robert M. Albrecht: [email protected]
            101475: '*****@*****.**',
            # Davide Cescato: [email protected]
            123204: '*****@*****.**',
            # Nick Bebout: [email protected]
            101458: '*****@*****.**',
            # Niels Haase: [email protected]
            126862: '*****@*****.**',
            # Thomas Janssen: [email protected]
            103110: '*****@*****.**',
            # Michael J Gruber: '*****@*****.**'
            105113: '*****@*****.**',
            # Juan Manuel Rodriguez Moreno: '*****@*****.**'
            101302: '*****@*****.**',
            # Andrew Cagney: '*****@*****.**'
            102169: '*****@*****.**',
            # Jeremy Katz: '*****@*****.**'
            100036: '*****@*****.**',
            # Dominic Hopf: '*****@*****.**'
            124904: '*****@*****.**',
            # Christoph Wickert: '*****@*****.**':
            100271: '*****@*****.**',
            # Elliott Baron: '*****@*****.**'
            106760: '*****@*****.**',
            # Thomas Spura: '*****@*****.**'
            111433: '*****@*****.**',
            # Adam Miller: '*****@*****.**'
            110673: '*****@*****.**',
            # Garrett Holmstrom: '*****@*****.**'
            131739: '*****@*****.**',
            # Tareq Al Jurf: [email protected]
            109863: '*****@*****.**',
            # Josh Kayse: [email protected]
            148243: '*****@*****.**',
            # Behdad Esfahbod: [email protected]
            100102: '*****@*****.**',
            # Daniel Bruno: [email protected]
            101608: '*****@*****.**',
            # Beth Lynn Eicher: [email protected]
            148706: '*****@*****.**',
            # Andre Robatino: [email protected]
            114970: '*****@*****.**',
            # Jeff Sheltren: [email protected]
            100058: '*****@*****.**',
            # Josh Boyer: [email protected]
            100115: '*****@*****.**',
            # Matthew Miller: [email protected]
            100042: '*****@*****.**',
            # Jamie Nguyen: [email protected]
            160587: '*****@*****.**',
            # Nikos Roussos: [email protected]
            144436: '*****@*****.**',
            # Benedikt Schäfer: [email protected]
            154726: '*****@*****.**',
            # Ricky Elrod: [email protected]
            139137: '*****@*****.**',
            # David Xie: [email protected]
            167133: '*****@*****.**',
            # Felix Schwarz: [email protected]
            103551: '*****@*****.**',
            # Martin Holec: [email protected]
            137561: '*****@*****.**',
            # John Dulaney: [email protected]
            149140: '*****@*****.**',
            # Niels de Vos: [email protected]
            102792: '*****@*****.**',
            # Shawn Wells: [email protected]
            156515: '*****@*****.**',
            # Christopher Tubbs: [email protected]
            160404: '*****@*****.**',
            # Björn Esser: [email protected]
            163460: '*****@*****.**',
            # Amit Shah: [email protected]
            115389: '*****@*****.**',
            # Mark Wielard: [email protected]
            102697: '*****@*****.**',
            # Benjamin Lefoul: [email protected]
            189661: '*****@*****.**',
            # Mike Ruckman: [email protected]
            172063: '*****@*****.**',
            # Dusty Mabe: [email protected]
            170115: '*****@*****.**',
            # Ben Williams: [email protected]
            100572: '*****@*****.**',
            # David Dykstra: [email protected]
            216452: '*****@*****.**',
            # Till Hofmann: <*****@*****.**>
            175557: '*****@*****.**',
        }
        # A few people have an email account that is used in owners.list but
        # have setup a bugzilla account for their primary account system email
        # address now.  Map these here.
        self.__alternate_email = {
            # Damien Durand: [email protected]
            '*****@*****.**': 100406,
            # Kevin Fenzi: [email protected]
            '*****@*****.**': 100037,
        }
        for bugzilla_map in self.__bugzilla_email.items():
            self.__alternate_email[bugzilla_map[1]] = bugzilla_map[0]
コード例 #7
0
    def __init__(self,
                 base_url='https://admin.fedoraproject.org/accounts/',
                 *args,
                 **kwargs):
        '''Create the AccountSystem client object.

        :kwargs base_url: Base of every URL used to contact the server.
            Defaults to the Fedora Project FAS instance.
        :kwargs useragent: useragent string to use.  If not given, default to
            "Fedora Account System Client/VERSION"
        :kwargs debug: If True, log debug information
        :kwargs username: username for establishing authenticated connections
        :kwargs password: password to use with authenticated connections
        :kwargs session_cookie: **Deprecated** Use session_id instead.
            User's session_cookie to connect to the server
        :kwargs session_id: user's session_id to connect to the server
        :kwargs cache_session: if set to true, cache the user's session cookie
            on the filesystem between runs.
        '''
        if 'useragent' not in kwargs:
            kwargs['useragent'] = \
                'Fedora Account System Client/%s' % __version__

        super(AccountSystem, self).__init__(base_url, *args, **kwargs)
        # We need a single proxy for the class to verify username/passwords
        # against.
        if not self.proxy:
            self.proxy = FasProxyClient(base_url,
                                        useragent=self.useragent,
                                        session_as_cookie=False,
                                        debug=self.debug,
                                        insecure=self.insecure)

        # Preseed a list of FAS accounts with bugzilla addresses
        # This allows us to specify a different email for bugzilla than is
        # in the FAS db.  It is a hack, however, until FAS has a field for the
        # bugzilla address.
        self.__bugzilla_email = {
            # Konstantin Ryabitsev: [email protected]
            100029: '*****@*****.**',
            # Sean Reifschneider: [email protected]
            100488: '*****@*****.**',
            # Karen Pease: [email protected]
            100281: '*****@*****.**',
            # Robert Scheck: [email protected]
            100093: '*****@*****.**',
            # Scott Bakers: [email protected]
            100881: '*****@*****.**',
            # Colin Charles: [email protected]
            100014: '*****@*****.**',
            # W. Michael Petullo: [email protected]
            100136: '*****@*****.**',
            # Elliot Lee: [email protected]
            100060: '*****@*****.**',
            # Control Center Team: Bugzilla user but email doesn't exist
            9908: '*****@*****.**',
            # Máirín Duffy
            100548: '*****@*****.**',
            # Muray McAllister: [email protected]
            102321: '*****@*****.**',
            # William Jon McCann: [email protected]
            102489: '*****@*****.**',
            # Matt Domsch's rebuild script -- bz email goes to /dev/null
            103590: '*****@*****.**',
            # Sindre Pedersen Bjørdal: [email protected]
            100460: '*****@*****.**',
            # Jesus M. Rodriguez: [email protected]
            102180: '*****@*****.**',
            # Roozbeh Pournader: [email protected]
            100350: '*****@*****.**',
            # Michael DeHaan: [email protected]
            100603: '*****@*****.**',
            # Sebastian Gosenheimer: [email protected]
            103647: '*****@*****.**',
            # Ben Konrath: [email protected]
            101156: '*****@*****.**',
            # Kai Engert: [email protected]
            100399: '*****@*****.**',
            # William Jon McCann: [email protected]
            102952: '*****@*****.**',
            # Simon Wesp: [email protected]
            109464: '*****@*****.**',
            # Robert M. Albrecht: [email protected]
            101475: '*****@*****.**',
            # Davide Cescato: [email protected]
            123204: '*****@*****.**',
            # Nick Bebout: [email protected]
            101458: '*****@*****.**',
            # Niels Haase: [email protected]
            126862: '*****@*****.**',
            # Thomas Janssen: [email protected]
            103110: '*****@*****.**',
            # Michael J Gruber: '*****@*****.**'
            105113: '*****@*****.**',
            # Juan Manuel Rodriguez Moreno: '*****@*****.**'
            101302: '*****@*****.**',
            # Andrew Cagney: '*****@*****.**'
            102169: '*****@*****.**',
            # Jeremy Katz: '*****@*****.**'
            100036: '*****@*****.**',
            # Dominic Hopf: '*****@*****.**'
            124904: '*****@*****.**',
            # Christoph Wickert: '*****@*****.**':
            100271: '*****@*****.**',
            # Elliott Baron: '*****@*****.**'
            106760: '*****@*****.**',
            # Thomas Spura: '*****@*****.**'
            111433: '*****@*****.**',
            # Adam Miller: '*****@*****.**'
            110673: '*****@*****.**',
            # Garrett Holmstrom: '*****@*****.**'
            131739: '*****@*****.**',
            # Tareq Al Jurf: [email protected]
            109863: '*****@*****.**',
            # Josh Kayse: [email protected]
            148243: '*****@*****.**',
            # Behdad Esfahbod: [email protected]
            100102: '*****@*****.**',
            # Daniel Bruno: [email protected]
            101608: '*****@*****.**',
            # Beth Lynn Eicher: [email protected]
            148706: '*****@*****.**',
            # Andre Robatino: [email protected]
            114970: '*****@*****.**',
            # Jeff Sheltren: [email protected]
            100058: '*****@*****.**',
            # Josh Boyer: [email protected]
            100115: '*****@*****.**',
            # Matthew Miller: [email protected]
            100042: '*****@*****.**',
            # Jamie Nguyen: [email protected]
            160587: '*****@*****.**',
            # Nikos Roussos: [email protected]
            144436: '*****@*****.**',
            # Benedikt Schäfer: [email protected]
            154726: '*****@*****.**',
            # Ricky Elrod: [email protected]
            139137: '*****@*****.**',
            # David Xie: [email protected]
            167133: '*****@*****.**',
            # Felix Schwarz: [email protected]
            103551: '*****@*****.**',
            # Martin Holec: [email protected]
            137561: '*****@*****.**',
            # John Dulaney: [email protected]
            149140: '*****@*****.**',
            # Niels de Vos: [email protected]
            102792: '*****@*****.**',
            # Shawn Wells: [email protected]
            156515: '*****@*****.**',
            # Christopher Tubbs: [email protected]
            160404: '*****@*****.**',
        }
        # A few people have an email account that is used in owners.list but
        # have setup a bugzilla account for their primary account system email
        # address now.  Map these here.
        self.__alternate_email = {
            # Damien Durand: [email protected]
            '*****@*****.**': 100406,
            # Kevin Fenzi: [email protected]
            '*****@*****.**': 100037,
        }
        for bugzilla_map in self.__bugzilla_email.items():
            self.__alternate_email[bugzilla_map[1]] = bugzilla_map[0]
コード例 #8
0
class AccountSystem(BaseClient):
    '''An object for querying the Fedora Account System.

    The Account System object provides a python API for talking to the Fedora
    Account System.  It abstracts the http requests, cookie handling, and
    other details so you can concentrate on the methods that are important to
    your program.

    .. warning::

        If your code is trying to use the AccountSystem object to
        connect to fas for multiple users you probably want to use
        :class:`~fedora.client.FasProxyClient` instead.  If your code is
        trying to reuse a single instance of AccountSystem for multiple users
        you *definitely* want to use :class:`~fedora.client.FasProxyClient`
        instead.  Using AccountSystem in these cases may result in a user
        being logged in as a different user.  (This may be the case even if
        you instantiate a new AccountSystem object for each user if
        :attr:cache_session: is True since that creates a file on the file
        system that can end up loading session credentials for the wrong
        person.

    .. versionchanged:: 0.3.26
        Added :meth:`~fedora.client.AccountSystem.gravatar_url` that returns
        a url to a gravatar for a user.
    .. versionchanged:: 0.3.33
        Renamed :meth:`~fedora.client.AccountSystem.gravatar_url` to
        :meth:`~fedora.client.AccountSystem.avatar_url`.
    '''
    # proxy is a thread-safe connection to the fas server for verifying
    # passwords of other users
    proxy = None

    # size that we allow to request from remote avatar providers.
    _valid_avatar_sizes = (32, 64, 140)
    # URLs for remote avatar providers.
    _valid_avatar_services = ['libravatar', 'gravatar']

    def __init__(self,
                 base_url='https://admin.fedoraproject.org/accounts/',
                 *args,
                 **kwargs):
        '''Create the AccountSystem client object.

        :kwargs base_url: Base of every URL used to contact the server.
            Defaults to the Fedora Project FAS instance.
        :kwargs useragent: useragent string to use.  If not given, default to
            "Fedora Account System Client/VERSION"
        :kwargs debug: If True, log debug information
        :kwargs username: username for establishing authenticated connections
        :kwargs password: password to use with authenticated connections
        :kwargs session_cookie: **Deprecated** Use session_id instead.
            User's session_cookie to connect to the server
        :kwargs session_id: user's session_id to connect to the server
        :kwargs cache_session: if set to true, cache the user's session cookie
            on the filesystem between runs.
        '''
        if 'useragent' not in kwargs:
            kwargs['useragent'] = \
                'Fedora Account System Client/%s' % __version__

        super(AccountSystem, self).__init__(base_url, *args, **kwargs)
        # We need a single proxy for the class to verify username/passwords
        # against.
        if not self.proxy:
            self.proxy = FasProxyClient(base_url,
                                        useragent=self.useragent,
                                        session_as_cookie=False,
                                        debug=self.debug,
                                        insecure=self.insecure)

        # Preseed a list of FAS accounts with bugzilla addresses
        # This allows us to specify a different email for bugzilla than is
        # in the FAS db.  It is a hack, however, until FAS has a field for the
        # bugzilla address.
        self.__bugzilla_email = {
            # Konstantin Ryabitsev: [email protected]
            100029: '*****@*****.**',
            # Sean Reifschneider: [email protected]
            100488: '*****@*****.**',
            # Karen Pease: [email protected]
            100281: '*****@*****.**',
            # Robert Scheck: [email protected]
            100093: '*****@*****.**',
            # Scott Bakers: [email protected]
            100881: '*****@*****.**',
            # Colin Charles: [email protected]
            100014: '*****@*****.**',
            # W. Michael Petullo: [email protected]
            100136: '*****@*****.**',
            # Elliot Lee: [email protected]
            100060: '*****@*****.**',
            # Control Center Team: Bugzilla user but email doesn't exist
            9908: '*****@*****.**',
            # Máirín Duffy
            100548: '*****@*****.**',
            # Muray McAllister: [email protected]
            102321: '*****@*****.**',
            # William Jon McCann: [email protected]
            102489: '*****@*****.**',
            # Matt Domsch's rebuild script -- bz email goes to /dev/null
            103590: '*****@*****.**',
            # Sindre Pedersen Bjørdal: [email protected]
            100460: '*****@*****.**',
            # Jesus M. Rodriguez: [email protected]
            102180: '*****@*****.**',
            # Roozbeh Pournader: [email protected]
            100350: '*****@*****.**',
            # Michael DeHaan: [email protected]
            100603: '*****@*****.**',
            # Sebastian Gosenheimer: [email protected]
            103647: '*****@*****.**',
            # Ben Konrath: [email protected]
            101156: '*****@*****.**',
            # Kai Engert: [email protected]
            100399: '*****@*****.**',
            # William Jon McCann: [email protected]
            102952: '*****@*****.**',
            # Simon Wesp: [email protected]
            109464: '*****@*****.**',
            # Robert M. Albrecht: [email protected]
            101475: '*****@*****.**',
            # Davide Cescato: [email protected]
            123204: '*****@*****.**',
            # Nick Bebout: [email protected]
            101458: '*****@*****.**',
            # Niels Haase: [email protected]
            126862: '*****@*****.**',
            # Thomas Janssen: [email protected]
            103110: '*****@*****.**',
            # Michael J Gruber: '*****@*****.**'
            105113: '*****@*****.**',
            # Juan Manuel Rodriguez Moreno: '*****@*****.**'
            101302: '*****@*****.**',
            # Andrew Cagney: '*****@*****.**'
            102169: '*****@*****.**',
            # Jeremy Katz: '*****@*****.**'
            100036: '*****@*****.**',
            # Dominic Hopf: '*****@*****.**'
            124904: '*****@*****.**',
            # Christoph Wickert: '*****@*****.**':
            100271: '*****@*****.**',
            # Elliott Baron: '*****@*****.**'
            106760: '*****@*****.**',
            # Thomas Spura: '*****@*****.**'
            111433: '*****@*****.**',
            # Adam Miller: '*****@*****.**'
            110673: '*****@*****.**',
            # Garrett Holmstrom: '*****@*****.**'
            131739: '*****@*****.**',
            # Tareq Al Jurf: [email protected]
            109863: '*****@*****.**',
            # Josh Kayse: [email protected]
            148243: '*****@*****.**',
            # Behdad Esfahbod: [email protected]
            100102: '*****@*****.**',
            # Daniel Bruno: [email protected]
            101608: '*****@*****.**',
            # Beth Lynn Eicher: [email protected]
            148706: '*****@*****.**',
            # Andre Robatino: [email protected]
            114970: '*****@*****.**',
            # Jeff Sheltren: [email protected]
            100058: '*****@*****.**',
            # Josh Boyer: [email protected]
            100115: '*****@*****.**',
            # Matthew Miller: [email protected]
            100042: '*****@*****.**',
            # Jamie Nguyen: [email protected]
            160587: '*****@*****.**',
            # Nikos Roussos: [email protected]
            144436: '*****@*****.**',
            # Benedikt Schäfer: [email protected]
            154726: '*****@*****.**',
            # Ricky Elrod: [email protected]
            139137: '*****@*****.**',
            # David Xie: [email protected]
            167133: '*****@*****.**',
            # Felix Schwarz: [email protected]
            103551: '*****@*****.**',
            # Martin Holec: [email protected]
            137561: '*****@*****.**',
            # John Dulaney: [email protected]
            149140: '*****@*****.**',
            # Niels de Vos: [email protected]
            102792: '*****@*****.**',
            # Shawn Wells: [email protected]
            156515: '*****@*****.**',
            # Christopher Tubbs: [email protected]
            160404: '*****@*****.**',
        }
        # A few people have an email account that is used in owners.list but
        # have setup a bugzilla account for their primary account system email
        # address now.  Map these here.
        self.__alternate_email = {
            # Damien Durand: [email protected]
            '*****@*****.**': 100406,
            # Kevin Fenzi: [email protected]
            '*****@*****.**': 100037,
        }
        for bugzilla_map in self.__bugzilla_email.items():
            self.__alternate_email[bugzilla_map[1]] = bugzilla_map[0]

        # We use the two mappings as follows::
        # When looking up a user by email, use __alternate_email.
        # When looking up a bugzilla email address use __bugzilla_email.
        #
        # This allows us to parse in owners.list and have a value for all the
        # emails in there while not using the alternate email unless it is
        # the only option.

    # TODO: Use exceptions properly

    ### Set insecure properly ###
    # When setting insecure, we have to set it both on ourselves and on
    # self.proxy
    def _get_insecure(self):
        return self._insecure

    def _set_insecure(self, insecure):
        self._insecure = insecure
        self.proxy = FasProxyClient(self.base_url,
                                    useragent=self.useragent,
                                    session_as_cookie=False,
                                    debug=self.debug,
                                    insecure=insecure)
        return insecure

    #: If this attribute is set to True, do not check server certificates
    #: against their CA's.  This means that man-in-the-middle attacks are
    #: possible. You might turn this option on for testing against a local
    #: version of a server with a self-signed certificate but it should be off
    #: in production.
    insecure = property(_get_insecure, _set_insecure)

    ### Groups ###

    def create_group(self,
                     name,
                     display_name,
                     owner,
                     group_type,
                     invite_only=0,
                     needs_sponsor=0,
                     user_can_remove=1,
                     prerequisite='',
                     joinmsg='',
                     apply_rules='None'):
        '''Creates a FAS group.

        :arg name: The short group name (alphanumeric only).
        :arg display_name: A longer version of the group's name.
        :arg owner: The username of the FAS account which owns the new group.
        :arg group_type: The kind of group being created. Current valid options
            are git, svn, hg, shell, and tracking.
        :kwarg invite_only: Users must be invited to the group, they cannot
            join on their own.
        :kwarg needs_sponsor: Users must be sponsored into the group.
        :kwarg user_can_remove: Users can remove themselves from the group.
        :kwarg prerequisite: Users must be in the given group (string) before
            they can join the new group.
        :kwarg joinmsg: A message shown to users when they apply to the group.
        :kwarg apply_rules: Rules for applying to the group, shown to users
            before they apply.
        :rtype: :obj:`bunch.Bunch`
        :returns: A Bunch containing information about the group that was
            created.

        .. versionadded:: 0.3.29
        '''
        req_params = {
            'invite_only': invite_only,
            'needs_sponsor': needs_sponsor,
            'user_can_remove': user_can_remove,
            'prerequisite': prerequisite,
            'joinmsg': joinmsg,
            'apply_rules': apply_rules
        }

        request = self.send_request(
            '/group/create/%s/%s/%s/%s' %
            (urllib.quote(name), urllib.quote(display_name),
             urllib.quote(owner), urllib.quote(group_type)),
            req_params=req_params,
            auth=True)
        return request

    def group_by_id(self, group_id):
        '''Returns a group object based on its id'''
        params = {'group_id': int(group_id)}
        request = self.send_request('json/group_by_id',
                                    auth=True,
                                    req_params=params)
        if request['success']:
            return request['group']
        else:
            return dict()

    def group_by_name(self, groupname):
        '''Returns a group object based on its name'''
        params = {'groupname': groupname}
        request = self.send_request('json/group_by_name',
                                    auth=True,
                                    req_params=params)
        if request['success']:
            return request['group']
        else:
            raise AppError(message='FAS server unable to retrieve group'
                           ' %(group)s' % {'group': to_bytes(groupname)},
                           name='FASError')

    def group_members(self, groupname):
        '''Return a list of people approved for a group.

        This method returns a list of people who are in the requested group.
        The people are all approved in the group.  Unapproved people are not
        shown.  The format of data is::

            \[{'username': '******', 'role_type': 'user'},
            \{'username': '******', 'role_type': 'sponsor'}]

        role_type can be one of 'user', 'sponsor', or 'administrator'.

        .. versionadded:: 0.3.2
        .. versionchanged:: 0.3.21
            Return a Bunch instead of a DictContainer
        '''
        request = self.send_request('/group/dump/%s' % urllib.quote(groupname),
                                    auth=True)

        return [
            Bunch(username=user[0], role_type=user[3])
            for user in request['people']
        ]

    ### People ###

    def person_by_id(self, person_id):
        '''Returns a person object based on its id'''
        person_id = int(person_id)
        params = {'person_id': person_id}
        request = self.send_request('json/person_by_id',
                                    auth=True,
                                    req_params=params)

        if request['success']:
            if person_id in self.__bugzilla_email:
                request['person']['bugzilla_email'] = \
                    self.__bugzilla_email[person_id]
            else:
                request['person']['bugzilla_email'] = \
                    request['person']['email']

            # In a devel version of FAS, membership info was returned
            # separately
            # This was later corrected (can remove this code at some point)
            if 'approved' in request:
                request['person']['approved_memberships'] = request['approved']
            if 'unapproved' in request:
                request['person']['unapproved_memberships'] = \
                    request['unapproved']
            return request['person']
        else:
            return dict()

    def person_by_username(self, username):
        '''Returns a person object based on its username'''
        params = {'username': username}
        request = self.send_request('json/person_by_username',
                                    auth=True,
                                    req_params=params)

        if request['success']:
            person = request['person']
            if person['id'] in self.__bugzilla_email:
                person['bugzilla_email'] = self.__bugzilla_email[person['id']]
            else:
                person['bugzilla_email'] = person['email']
            # In a devel version of FAS, membership info was returned
            # separately
            # This was later corrected (can remove this code at some point)
            if 'approved' in request:
                request['person']['approved_memberships'] = request['approved']
            if 'unapproved' in request:
                request['person']['unapproved_memberships'] = \
                    request['unapproved']
            return person
        else:
            return dict()

    def avatar_url(self,
                   username,
                   size=64,
                   default=None,
                   lookup_email=True,
                   service=None):
        ''' Returns a URL to an avatar for a given username.

        Avatars are drawn from third party services.

        :arg username: FAS username to construct a avatar url for
        :kwarg size: size of the avatar.  Allowed sizes are 32, 64, 140.
            Default: 64
        :kwarg default: If the service does not have a avatar image for the
            email address, this url is returned instead.  Default:
            the fedora logo at the specified size.
        :kwarg lookup_email:  If true, use the email from FAS for gravatar.com
            lookups, otherwise just append @fedoraproject.org to the username.
            For libravatar.org lookups, this is ignored.  The openid identifier
            of the user is used instead.
            Note that gravatar.com lookups will be much slower if lookup_email
            is set to True since we'd have to make a query against FAS itself.
        :kwarg service: One of 'libravatar' or 'gravatar'.
            Default: 'libravatar'.
        :raises ValueError: if the size parameter is not allowed or if the
            service is not one of 'libravatar' or 'gravatar'
        :rtype: :obj:`str`
        :returns: url of a avatar for the user

        If that user has no avatar entry, instruct the remote service to
        redirect us to the Fedora logo.

        If that user has no email attribute, then make a fake request to
        the third party service.

        .. versionadded:: 0.3.26
        .. versionchanged: 0.3.30
            Add lookup_email parameter to control whether we generate avatar
            urls with the email in fas or [email protected]
        .. versionchanged: 0.3.33
            Renamed from `gravatar_url` to `avatar_url`
        .. versionchanged: 0.3.34
            Updated libravatar to use the user's openid identifier.
        '''

        if size not in self._valid_avatar_sizes:
            raise ValueError(
                'Size %(size)i disallowed.  Must be in %(valid_sizes)r' % {
                    'size': size,
                    'valid_sizes': self._valid_avatar_sizes
                })

        # If our caller explicitly requested libravatar but they don't have
        # it installed, then we need to raise a nice error and let them know.
        if service == 'libravatar' and not libravatar:
            raise ValueError("Install python-pylibravatar if you want to "
                             "use libravatar as an avatar provider.")

        # If our caller didn't specify a service, let's pick a one for them.
        # If they have pylibravatar installed, then by all means let freedom
        # ring!  Otherwise, we'll use gravatar.com if we have to.
        if not service:
            if libravatar:
                service = 'libravatar'
            else:
                service = 'gravatar'

        # Just double check to make sure they didn't pass us a bogus service.
        if service not in self._valid_avatar_services:
            raise ValueError('Service %(service)r disallowed. '
                             'Must be in %(valid_services)r' % {
                                 'service': service,
                                 'valid_services': self._valid_avatar_services
                             })

        if not default:
            default = "http://fedoraproject.org/static/images/" + \
                      "fedora_infinity_%ix%i.png" % (size, size)

        if service == 'libravatar':
            openid = 'http://%s.id.fedoraproject.org/' % username
            return libravatar.libravatar_url(
                openid=openid,
                size=size,
                default=default,
            )
        else:
            if lookup_email:
                person = self.person_by_username(username)
                email = person.get('email', 'no_email')
            else:
                email = "*****@*****.**" % username

            query_string = urllib.urlencode({
                's': size,
                'd': default,
            })

            hash = md5(email).hexdigest()

            return "http://www.gravatar.com/avatar/%s?%s" % (hash,
                                                             query_string)

    def gravatar_url(self, *args, **kwargs):
        """ *Deprecated* - Use avatar_url.

         .. versionadded:: 0.3.26
         .. versionchanged: 0.3.30
            Add lookup_email parameter to control whether we generate gravatar
            urls with the email in fas or [email protected]
         .. versionchanged: 0.3.33
            Deprecated in favor of `avatar_url`.
        """

        warnings.warn(
            "gravatar_url is deprecated and will be removed in"
            " a future version.  Please port your code to use avatar_url(...,"
            " service='libravatar', ...)  instead",
            DeprecationWarning,
            stacklevel=2)

        if 'service' in kwargs:
            raise TypeError("'service' is an invalid keyword argument for"
                            " this function.  Use avatar_url() instead)")

        return self.avatar_url(*args, service='gravatar', **kwargs)

    def user_id(self):
        '''Returns a dict relating user IDs to usernames'''
        request = self.send_request('json/user_id', auth=True)
        people = {}
        for person_id, username in request['people'].items():
            # change userids from string back to integer
            people[int(person_id)] = username
        return people

    def people_by_key(self, key=u'username', search=u'*', fields=None):
        '''Return a dict of people

        :kwarg key: Key by this field.  Valid values are 'id', 'username', or
            'email'.  Default is 'username'
        :kwarg search: Pattern to match usernames against.  Defaults to the
            '*' wildcard which matches everyone.
        :kwarg fields: Limit the data returned to a specific list of fields.
            The default is to retrieve all fields.
            Valid fields are:

                * affiliation
                * alias_enabled
                * bugzilla_email
                * certificate_serial
                * comments
                * country_code
                * creation
                * email
                * emailtoken
                * facsimile
                * gpg_keyid
                * group_roles
                * human_name
                * id
                * internal_comments
                * ircnick
                * last_seen
                * latitude
                * locale
                * longitude
                * memberships
                * old_password
                * password
                * password_changed
                * passwordtoken
                * postal_address
                * privacy
                * roles
                * ssh_key
                * status
                * status_change
                * telephone
                * timezone
                * unverified_email
                * username

            Note that for most users who access this data, many of these
            fields will be set to None due to security or privacy settings.
        :returns: a dict relating the key value to the fields.

        .. versionchanged:: 0.3.21
            Return a Bunch instead of a DictContainer
        .. versionchanged:: 0.3.26
            Fixed to return a list with both people who have signed the CLA
            and have not
        '''
        # Make sure we have a valid key value
        if key not in ('id', 'username', 'email'):
            raise KeyError('key must be one of "id", "username", or'
                           ' "email"')

        if fields:
            fields = list(fields)
            for field in fields:
                if field not in USERFIELDS:
                    raise KeyError('%(field)s is not a valid field to'
                                   ' filter' % {'field': to_bytes(field)})
        else:
            fields = USERFIELDS

        # Make sure we retrieve the key value
        unrequested_fields = []
        if key not in fields:
            unrequested_fields.append(key)
            fields.append(key)
        if 'bugzilla_email' in fields:
            # Need id and email for the bugzilla information
            if 'id' not in fields:
                unrequested_fields.append('id')
                fields.append('id')
            if 'email' not in fields:
                unrequested_fields.append('email')
                fields.append('email')

        request = self.send_request(
            '/user/list',
            req_params={
                'search': search,
                'fields': [f for f in fields if f != 'bugzilla_email']
            },
            auth=True)

        people = Bunch()
        for person in itertools.chain(request['people'],
                                      request['unapproved_people']):
            # Retrieve bugzilla_email from our list if necessary
            if 'bugzilla_email' in fields:
                if person['id'] in self.__bugzilla_email:
                    person['bugzilla_email'] = \
                        self.__bugzilla_email[person['id']]
                else:
                    person['bugzilla_email'] = person['email']

            person_key = person[key]
            # Remove any fields that weren't requested by the user
            if unrequested_fields:
                for field in unrequested_fields:
                    del person[field]

            # Add the person record to the people dict
            people[person_key] = person

        return people

    def people_by_id(self):
        '''*Deprecated* Use people_by_key() instead.

        Returns a dict relating user IDs to human_name, email, username,
        and bugzilla email

        .. versionchanged:: 0.3.21
            Return a Bunch instead of a DictContainer
        '''
        warnings.warn(
            "people_by_id() is deprecated and will be removed in"
            " 0.4.  Please port your code to use people_by_key(key='id',"
            " fields=['human_name', 'email', 'username', 'bugzilla_email'])"
            " instead",
            DeprecationWarning,
            stacklevel=2)

        request = self.send_request('/json/user_id', auth=True)
        user_to_id = {}
        people = Bunch()
        for person_id, username in request['people'].items():
            person_id = int(person_id)
            # change userids from string back to integer
            people[person_id] = {'username': username, 'id': person_id}
            user_to_id[username] = person_id

        # Retrieve further useful information about the users
        request = self.send_request('/group/dump', auth=True)
        for user in request['people']:
            userid = user_to_id[user[0]]
            person = people[userid]
            person['email'] = user[1]
            person['human_name'] = user[2]
            if userid in self.__bugzilla_email:
                person['bugzilla_email'] = self.__bugzilla_email[userid]
            else:
                person['bugzilla_email'] = person['email']

        return people

    ### Utils ###

    def people_by_groupname(self, groupname):
        '''Return a list of persons for the given groupname.

        :arg groupname: Name of the group to look up
        :returns: A list of person objects from the group.  If the group
            contains no entries, then an empty list is returned.
        '''
        people = self.people_by_id()
        group = dict(self.group_by_name(groupname))
        userids = [
            user[u'person_id']
            for user in group[u'approved_roles'] + group[u'unapproved_roles']
        ]
        return [people[userid] for userid in userids]

    ### Configs ###

    def get_config(self, username, application, attribute):
        '''Return the config entry for the key values.

        :arg username: Username of the person
        :arg application: Application for which the config is set
        :arg attribute: Attribute key to lookup
        :raises AppError: if the server returns an exception
        :returns: The unicode string that describes the value.  If no entry
            matched the username, application, and attribute then None is
            returned.
        '''
        request = self.send_request('config/list/%s/%s/%s' %
                                    (username, application, attribute),
                                    auth=True)
        if 'exc' in request:
            raise AppError(name=request['exc'], message=request['tg_flash'])

        # Return the value if it exists, else None.
        if 'configs' in request and attribute in request['configs']:
            return request['configs'][attribute]
        return None

    def get_configs_like(self, username, application, pattern=u'*'):
        '''Return the config entries that match the keys and the pattern.

        Note: authentication on the server will prevent anyone but the user
        or a fas admin from viewing or changing their configs.

        :arg username: Username of the person
        :arg application: Application for which the config is set
        :kwarg pattern: A pattern to select values for.  This accepts * as a
            wildcard character. Default='*'
        :raises AppError: if the server returns an exception
        :returns: A dict mapping ``attribute`` to ``value``.
        '''
        request = self.send_request('config/list/%s/%s/%s' %
                                    (username, application, pattern),
                                    auth=True)
        if 'exc' in request:
            raise AppError(name=request['exc'], message=request['tg_flash'])

        return request['configs']

    def set_config(self, username, application, attribute, value):
        '''Set a config entry in FAS for the user.

        Note: authentication on the server will prevent anyone but the user
        or a fas admin from viewing or changing their configs.

        :arg username: Username of the person
        :arg application: Application for which the config is set
        :arg attribute: The name of the config key that we're setting
        :arg value: The value to set this to
        :raises AppError: if the server returns an exception
        '''
        request = self.send_request('config/set/%s/%s/%s' %
                                    (username, application, attribute),
                                    req_params={'value': value},
                                    auth=True)

        if 'exc' in request:
            raise AppError(name=request['exc'], message=request['tg_flash'])

    def people_query(self, constraints=None, columns=None):
        '''Returns a list of dicts representing database rows

        :arg constraints: A dictionary specifying WHERE constraints on columns
        :arg columns: A list of columns to be selected in the query
        :raises AppError: if the query failed on the server (most likely
            because  the server was given a bad query)
        :returns: A list of dicts representing database rows (the keys of
            the dict are the columns requested)

        .. versionadded:: 0.3.12.1
        '''
        if constraints is None:
            constraints = {}
        if columns is None:
            columns = []

        req_params = {}
        req_params.update(constraints)
        req_params['columns'] = ','.join(columns)

        try:
            request = self.send_request('json/people_query',
                                        req_params=req_params,
                                        auth=True)
            if request['success']:
                return request['data']
            else:
                raise AppError(message=request['error'], name='FASError')
        except FedoraServiceError:
            raise

    ### Certs ###

    def user_gencert(self):
        '''Generate a cert for a user'''
        try:
            request = self.send_request('user/dogencert', auth=True)
        except FedoraServiceError:
            raise
        if not request['cla']:
            raise CLAError
        return "%(cert)s\n%(key)s" % request

    ### Passwords ###

    def verify_password(self, username, password):
        '''Return whether the username and password pair are valid.

        :arg username: username to try authenticating
        :arg password: password for the user
        :returns: True if the username/password are valid.  False otherwise.
        '''
        return self.proxy.verify_password(username, password)

    ### fasClient Special Methods ###

    def group_data(self, force_refresh=None):
        '''Return administrators/sponsors/users and group type for all groups

        :arg force_refresh: If true, the returned data will be queried from the
            database, as opposed to memcached.
        :raises AppError: if the query failed on the server
        :returns: A dict mapping group names to the group type and the
            user IDs of the administrator, sponsors, and users of the group.

        .. versionadded:: 0.3.8
        '''
        params = {}
        if force_refresh:
            params['force_refresh'] = True

        try:
            request = self.send_request('json/fas_client/group_data',
                                        req_params=params,
                                        auth=True)
            if request['success']:
                return request['data']
            else:
                raise AppError(message='FAS server unable to retrieve'
                               ' group members',
                               name='FASError')
        except FedoraServiceError:
            raise

    def user_data(self):
        '''Return user data for all users in FAS

        Note: If the user is not authorized to see password hashes,
        '*' is returned for the hash.

        :raises AppError: if the query failed on the server
        :returns: A dict mapping user IDs to a username, password hash,
            SSH public key, email address, and status.

        .. versionadded:: 0.3.8
        '''
        try:
            request = self.send_request('json/fas_client/user_data', auth=True)
            if request['success']:
                return request['data']
            else:
                raise AppError(message='FAS server unable to retrieve user'
                               ' information',
                               name='FASError')
        except FedoraServiceError:
            raise
コード例 #9
0
ファイル: fas2.py プロジェクト: davidhrbac/cnucnu
    def __init__(self, base_url='https://admin.fedoraproject.org/accounts/',
            *args, **kwargs):
        '''Create the AccountSystem client object.

        :kwargs base_url: Base of every URL used to contact the server.
            Defaults to the Fedora Project FAS instance.
        :kwargs useragent: useragent string to use.  If not given, default to
            "Fedora Account System Client/VERSION"
        :kwargs debug: If True, log debug information
        :kwargs username: username for establishing authenticated connections
        :kwargs password: password to use with authenticated connections
        :kwargs session_cookie: **Deprecated** Use session_id instead.
            User's session_cookie to connect to the server
        :kwargs session_id: user's session_id to connect to the server
        :kwargs cache_session: if set to true, cache the user's session cookie
            on the filesystem between runs.
        '''
        if 'useragent' not in kwargs:
            kwargs['useragent'] = 'Fedora Account System Client/%s' \
                    % __version__
        super(AccountSystem, self).__init__(base_url, *args, **kwargs)
        # We need a single proxy for the class to verify username/passwords
        # against.
        if not self.proxy:
            self.proxy = FasProxyClient(base_url, useragent=self.useragent,
                    session_as_cookie=False, debug=self.debug)

        # Preseed a list of FAS accounts with bugzilla addresses
        # This allows us to specify a different email for bugzilla than is
        # in the FAS db.  It is a hack, however, until FAS has a field for the
        # bugzilla address.
        self.__bugzilla_email = {
                # Konstantin Ryabitsev: [email protected]
                100029: '*****@*****.**',
                # Sean Reifschneider: [email protected]
                100488: '*****@*****.**',
                # Karen Pease: [email protected]
                100281: '*****@*****.**',
                # Robert Scheck: [email protected]
                100093: '*****@*****.**',
                # Scott Bakers: [email protected]
                100881: '*****@*****.**',
                # Colin Charles: [email protected]
                100014: '*****@*****.**',
                # W. Michael Petullo: [email protected]
                100136: '*****@*****.**',
                # Elliot Lee: [email protected]
                100060: '*****@*****.**',
                # Control Center Team: Bugzilla user but email doesn't exist
                9908: '*****@*****.**',
                # Máirín Duffy
                100548: '*****@*****.**',
                # Muray McAllister: [email protected]
                102321: '*****@*****.**',
                # William Jon McCann: [email protected]
                102489: '*****@*****.**',
                # Matt Domsch's rebuild script -- bz email goes to /dev/null
                103590: '*****@*****.**',
                # Sindre Pedersen Bjørdal: [email protected]
                100460 : '*****@*****.**',
                # Jesus M. Rodriguez: [email protected]
                102180: '*****@*****.**',
                # Jeff Sheltren: [email protected]
                100058: '*****@*****.**',
                # Roozbeh Pournader: [email protected]
                100350: '*****@*****.**',
                # Michael DeHaan: [email protected]
                100603: '*****@*****.**',
                # Sebastian Gosenheimer: [email protected]
                103647: '*****@*****.**',
                # Ben Konrath: [email protected]
                101156: '*****@*****.**',
                # Kai Engert: [email protected]
                100399: '*****@*****.**',
                # William Jon McCann: [email protected]
                102952: '*****@*****.**',
                # Simon Wesp: [email protected]
                109464: '*****@*****.**',
                # Robert M. Albrecht: [email protected]
                101475: '*****@*****.**',
                # Mathieu Bridon: [email protected]
                100753: '*****@*****.**',
                # Davide Cescato: [email protected]
                123204: '*****@*****.**',
                # Nick Bebout: [email protected]
                101458: '*****@*****.**',
                # Niels Haase: [email protected]
                126862: '*****@*****.**',
                # Thomas Janssen: [email protected]
                103110: '*****@*****.**',
                # Michael J Gruber: '*****@*****.**'
                105113: '*****@*****.**',
                # Juan Manuel Rodriguez Moreno: '*****@*****.**'
                101302: '*****@*****.**',
                # Andrew Cagney: '*****@*****.**'
                102169: '*****@*****.**',
                # Jeremy Katz: '*****@*****.**'
                100036: '*****@*****.**',
                # Dominic Hopf: '*****@*****.**'
                124904: '*****@*****.**',
                # Christoph Wickert: '*****@*****.**':
                100271: '*****@*****.**',
                # Elliott Baron: '*****@*****.**'
                106760: '*****@*****.**',
                # Thomas Spura: '*****@*****.**'
                111433: '*****@*****.**',
                # Adam Miller: '*****@*****.**'
                110673: '*****@*****.**',
                # Garrett Holmstrom: '*****@*****.**'
                131739: '*****@*****.**',
                # Tareq Al Jurf: [email protected]
                109863: '*****@*****.**',
                }
        # A few people have an email account that is used in owners.list but
        # have setup a bugzilla account for their primary account system email
        # address now.  Map these here.
        self.__alternate_email = {
                # Damien Durand: [email protected]
                '*****@*****.**': 100406,
                # Kevin Fenzi: [email protected]
                '*****@*****.**': 100037,
                }
        for bugzilla_map in self.__bugzilla_email.items():
            self.__alternate_email[bugzilla_map[1]] = bugzilla_map[0]
コード例 #10
0
#
# python-fedora is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with python-fedora; if not, see <http://www.gnu.org/licenses/>
#
'''
.. moduleauthor:: Ignacio Vazquez-Abrams <*****@*****.**>
.. moduleauthor:: Toshio Kuratomi <*****@*****.**>
'''
from fedora.client import FasProxyClient

from django.conf import settings

connection = None

if not connection:
    connection = FasProxyClient(base_url=settings.FAS_URL,
                                useragent=settings.FAS_USERAGENT)


def person_by_id(userid):
    sid, userinfo = connection.person_by_id(userid, {
        'username': settings.FAS_USERNAME,
        'password': settings.FAS_PASSWORD
    })
    return userinfo
コード例 #11
0
ファイル: views.py プロジェクト: ralphbean/fedora-openhw2012
def login(username, password):
    fas = FasProxyClient()
    user = fas.get_user_info({"username": username, "password": password})[1]
    roles = [g.name for g in user["approved_memberships"]]
    return user, roles
コード例 #12
0
ファイル: views.py プロジェクト: gaybro8777/fedora-openhw2012
def login(username, password):
    fas = FasProxyClient()
    user = fas.get_user_info({'username': username, 'password': password})
    roles = [g.name for g in user[1]['approved_memberships']]
    return roles
コード例 #13
0
class AccountSystem(BaseClient):
    '''An object for querying the Fedora Account System.

    The Account System object provides a python API for talking to the Fedora
    Account System.  It abstracts the http requests, cookie handling, and
    other details so you can concentrate on the methods that are important to
    your program.

    .. warning::

        If your code is trying to use the AccountSystem object to
        connect to fas for multiple users you probably want to use
        :class:`~fedora.client.FasProxyClient` instead.  If your code is
        trying to reuse a single instance of AccountSystem for multiple users
        you *definitely* want to use :class:`~fedora.client.FasProxyClient`
        instead.  Using AccountSystem in these cases may result in a user
        being logged in as a different user.  (This may be the case even if
        you instantiate a new AccountSystem object for each user if
        :attr:cache_session: is True since that creates a file on the file
        system that can end up loading session credentials for the wrong
        person.

    .. versionchanged:: 0.3.26
        Added :meth:`~fedora.client.AccountSystem.gravatar_url` that returns
        a url to a gravatar for a user.
    .. versionchanged:: 0.3.33
        Renamed :meth:`~fedora.client.AccountSystem.gravatar_url` to
        :meth:`~fedora.client.AccountSystem.avatar_url`.
    '''
    # proxy is a thread-safe connection to the fas server for verifying
    # passwords of other users
    proxy = None

    # size that we allow to request from remote avatar providers.
    _valid_avatar_sizes = (32, 64, 140)
    # URLs for remote avatar providers.
    _valid_avatar_services = ['libravatar', 'gravatar']

    def __init__(self, base_url='https://admin.rpmfusion.org/accounts/',
                 *args, **kwargs):
        '''Create the AccountSystem client object.

        :kwargs base_url: Base of every URL used to contact the server.
            Defaults to the Fedora Project FAS instance.
        :kwargs useragent: useragent string to use.  If not given, default to
            "Fedora Account System Client/VERSION"
        :kwargs debug: If True, log debug information
        :kwargs username: username for establishing authenticated connections
        :kwargs password: password to use with authenticated connections
        :kwargs session_cookie: **Deprecated** Use session_id instead.
            User's session_cookie to connect to the server
        :kwargs session_id: user's session_id to connect to the server
        :kwargs cache_session: if set to true, cache the user's session cookie
            on the filesystem between runs.
        '''
        if 'useragent' not in kwargs:
            kwargs['useragent'] = \
                'Fedora Account System Client/%s' % __version__

        super(AccountSystem, self).__init__(base_url, *args, **kwargs)
        # We need a single proxy for the class to verify username/passwords
        # against.
        if not self.proxy:
            self.proxy = FasProxyClient(base_url, useragent=self.useragent,
                                        session_as_cookie=False,
                                        debug=self.debug,
                                        insecure=self.insecure)

        # Preseed a list of FAS accounts with bugzilla addresses
        # This allows us to specify a different email for bugzilla than is
        # in the FAS db.  It is a hack, however, until FAS has a field for the
        # bugzilla address.
        self.__bugzilla_email = {
            # Nicolas Chauvet
            160404: '*****@*****.**',
        }
        # A few people have an email account that is used in owners.list but
        # have setup a bugzilla account for their primary account system email
        # address now.  Map these here.
        self.__alternate_email = {
            # Damien Durand: [email protected]
            '*****@*****.**': 100406,
            # Kevin Fenzi: [email protected]
            '*****@*****.**': 100037,
        }
        for bugzilla_map in self.__bugzilla_email.items():
            self.__alternate_email[bugzilla_map[1]] = bugzilla_map[0]

        # We use the two mappings as follows::
        # When looking up a user by email, use __alternate_email.
        # When looking up a bugzilla email address use __bugzilla_email.
        #
        # This allows us to parse in owners.list and have a value for all the
        # emails in there while not using the alternate email unless it is
        # the only option.

    # TODO: Use exceptions properly

    ### Set insecure properly ###
    # When setting insecure, we have to set it both on ourselves and on
    # self.proxy
    def _get_insecure(self):
        return self._insecure

    def _set_insecure(self, insecure):
        self._insecure = insecure
        self.proxy = FasProxyClient(self.base_url, useragent=self.useragent,
                                    session_as_cookie=False, debug=self.debug,
                                    insecure=insecure)
        return insecure
    #: If this attribute is set to True, do not check server certificates
    #: against their CA's.  This means that man-in-the-middle attacks are
    #: possible. You might turn this option on for testing against a local
    #: version of a server with a self-signed certificate but it should be off
    #: in production.
    insecure = property(_get_insecure, _set_insecure)

    ### Groups ###

    def create_group(self, name, display_name, owner, group_type,
                     invite_only=0, needs_sponsor=0, user_can_remove=1,
                     prerequisite='', joinmsg='', apply_rules='None'):
        '''Creates a FAS group.

        :arg name: The short group name (alphanumeric only).
        :arg display_name: A longer version of the group's name.
        :arg owner: The username of the FAS account which owns the new group.
        :arg group_type: The kind of group being created. Current valid options
            are git, svn, hg, shell, and tracking.
        :kwarg invite_only: Users must be invited to the group, they cannot
            join on their own.
        :kwarg needs_sponsor: Users must be sponsored into the group.
        :kwarg user_can_remove: Users can remove themselves from the group.
        :kwarg prerequisite: Users must be in the given group (string) before
            they can join the new group.
        :kwarg joinmsg: A message shown to users when they apply to the group.
        :kwarg apply_rules: Rules for applying to the group, shown to users
            before they apply.
        :rtype: :obj:`bunch.Bunch`
        :returns: A Bunch containing information about the group that was
            created.

        .. versionadded:: 0.3.29
        '''
        req_params = {
            'invite_only': invite_only,
            'needs_sponsor': needs_sponsor,
            'user_can_remove': user_can_remove,
            'prerequisite': prerequisite,
            'joinmsg': joinmsg,
            'apply_rules': apply_rules
        }

        request = self.send_request(
            '/group/create/%s/%s/%s/%s' % (
                urllib.quote(name),
                urllib.quote(display_name),
                urllib.quote(owner),
                urllib.quote(group_type)),
            req_params=req_params,
            auth=True
        )
        return request

    def group_by_id(self, group_id):
        '''Returns a group object based on its id'''
        params = {'group_id': int(group_id)}
        request = self.send_request(
            'json/group_by_id',
            auth=True,
            req_params=params
        )
        if request['success']:
            return request['group']
        else:
            return dict()

    def group_by_name(self, groupname):
        '''Returns a group object based on its name'''
        params = {'groupname': groupname}
        request = self.send_request(
            'json/group_by_name',
            auth=True,
            req_params=params
        )
        if request['success']:
            return request['group']
        else:
            raise AppError(
                message='FAS server unable to retrieve group'
                ' %(group)s' % {'group': to_bytes(groupname)},
                name='FASError')

    def group_members(self, groupname):
        '''Return a list of people approved for a group.

        This method returns a list of people who are in the requested group.
        The people are all approved in the group.  Unapproved people are not
        shown.  The format of data is::

            \[{'username': '******', 'role_type': 'user'},
            \{'username': '******', 'role_type': 'sponsor'}]

        role_type can be one of 'user', 'sponsor', or 'administrator'.

        .. versionadded:: 0.3.2
        .. versionchanged:: 0.3.21
            Return a Bunch instead of a DictContainer
        '''
        request = self.send_request('/group/dump/%s' %
                                    urllib.quote(groupname), auth=True)

        return [Bunch(username=user[0],
                      role_type=user[3]) for user in request['people']]

    ### People ###

    def person_by_id(self, person_id):
        '''Returns a person object based on its id'''
        person_id = int(person_id)
        params = {'person_id': person_id}
        request = self.send_request('json/person_by_id', auth=True,
                                    req_params=params)

        if request['success']:
            if person_id in self.__bugzilla_email:
                request['person']['bugzilla_email'] = \
                    self.__bugzilla_email[person_id]
            else:
                request['person']['bugzilla_email'] = \
                    request['person']['email']

            # In a devel version of FAS, membership info was returned
            # separately
            # This was later corrected (can remove this code at some point)
            if 'approved' in request:
                request['person']['approved_memberships'] = request['approved']
            if 'unapproved' in request:
                request['person']['unapproved_memberships'] = \
                    request['unapproved']
            return request['person']
        else:
            return dict()

    def person_by_username(self, username):
        '''Returns a person object based on its username'''
        params = {'username': username}
        request = self.send_request(
            'json/person_by_username',
            auth=True,
            req_params=params)

        if request['success']:
            person = request['person']
            if person['id'] in self.__bugzilla_email:
                person['bugzilla_email'] = self.__bugzilla_email[person['id']]
            else:
                person['bugzilla_email'] = person['email']
            # In a devel version of FAS, membership info was returned
            # separately
            # This was later corrected (can remove this code at some point)
            if 'approved' in request:
                request['person']['approved_memberships'] = request['approved']
            if 'unapproved' in request:
                request['person']['unapproved_memberships'] = \
                    request['unapproved']
            return person
        else:
            return dict()

    def avatar_url(self, username, size=64,
                   default=None, lookup_email=True,
                   service=None):
        ''' Returns a URL to an avatar for a given username.

        Avatars are drawn from third party services.

        :arg username: FAS username to construct a avatar url for
        :kwarg size: size of the avatar.  Allowed sizes are 32, 64, 140.
            Default: 64
        :kwarg default: If the service does not have a avatar image for the
            email address, this url is returned instead.  Default:
            the fedora logo at the specified size.
        :kwarg lookup_email:  If true, use the email from FAS for gravatar.com
            lookups, otherwise just append @fedoraproject.org to the username.
            For libravatar.org lookups, this is ignored.  The openid identifier
            of the user is used instead.
            Note that gravatar.com lookups will be much slower if lookup_email
            is set to True since we'd have to make a query against FAS itself.
        :kwarg service: One of 'libravatar' or 'gravatar'.
            Default: 'libravatar'.
        :raises ValueError: if the size parameter is not allowed or if the
            service is not one of 'libravatar' or 'gravatar'
        :rtype: :obj:`str`
        :returns: url of a avatar for the user

        If that user has no avatar entry, instruct the remote service to
        redirect us to the Fedora logo.

        If that user has no email attribute, then make a fake request to
        the third party service.

        .. versionadded:: 0.3.26
        .. versionchanged: 0.3.30
            Add lookup_email parameter to control whether we generate avatar
            urls with the email in fas or [email protected]
        .. versionchanged: 0.3.33
            Renamed from `gravatar_url` to `avatar_url`
        .. versionchanged: 0.3.34
            Updated libravatar to use the user's openid identifier.
        '''

        if size not in self._valid_avatar_sizes:
            raise ValueError(
                'Size %(size)i disallowed.  Must be in %(valid_sizes)r' % {
                    'size': size,
                    'valid_sizes': self._valid_avatar_sizes
                }
            )

        # If our caller explicitly requested libravatar but they don't have
        # it installed, then we need to raise a nice error and let them know.
        if service == 'libravatar' and not libravatar:
            raise ValueError("Install python-pylibravatar if you want to "
                             "use libravatar as an avatar provider.")

        # If our caller didn't specify a service, let's pick a one for them.
        # If they have pylibravatar installed, then by all means let freedom
        # ring!  Otherwise, we'll use gravatar.com if we have to.
        if not service:
            if libravatar:
                service = 'libravatar'
            else:
                service = 'gravatar'

        # Just double check to make sure they didn't pass us a bogus service.
        if service not in self._valid_avatar_services:
            raise ValueError(
                'Service %(service)r disallowed. '
                'Must be in %(valid_services)r' % {
                    'service': service,
                    'valid_services': self._valid_avatar_services
                }
            )

        if not default:
            default = "http://fedoraproject.org/static/images/" + \
                      "fedora_infinity_%ix%i.png" % (size, size)

        if service == 'libravatar':
            openid = 'http://%s.id.fedoraproject.org/' % username
            return libravatar.libravatar_url(
                openid=openid,
                size=size,
                default=default,
            )
        else:
            if lookup_email:
                person = self.person_by_username(username)
                email = person.get('email', 'no_email')
            else:
                email = "*****@*****.**" % username

            query_string = urllib.urlencode({
                's': size,
                'd': default,
            })

            hash = md5(email).hexdigest()

            return "http://www.gravatar.com/avatar/%s?%s" % (
                hash, query_string)

    def gravatar_url(self, *args, **kwargs):
        """ *Deprecated* - Use avatar_url.

         .. versionadded:: 0.3.26
         .. versionchanged: 0.3.30
            Add lookup_email parameter to control whether we generate gravatar
            urls with the email in fas or [email protected]
         .. versionchanged: 0.3.33
            Deprecated in favor of `avatar_url`.
        """

        warnings.warn(
            "gravatar_url is deprecated and will be removed in"
            " a future version.  Please port your code to use avatar_url(...,"
            " service='libravatar', ...)  instead",
            DeprecationWarning, stacklevel=2)

        if 'service' in kwargs:
            raise TypeError("'service' is an invalid keyword argument for"
                            " this function.  Use avatar_url() instead)")

        return self.avatar_url(*args, service='gravatar', **kwargs)

    def user_id(self):
        '''Returns a dict relating user IDs to usernames'''
        request = self.send_request('json/user_id', auth=True)
        people = {}
        for person_id, username in request['people'].items():
            # change userids from string back to integer
            people[int(person_id)] = username
        return people

    def people_by_key(self, key=u'username', search=u'*', fields=None):
        '''Return a dict of people

        :kwarg key: Key by this field.  Valid values are 'id', 'username', or
            'email'.  Default is 'username'
        :kwarg search: Pattern to match usernames against.  Defaults to the
            '*' wildcard which matches everyone.
        :kwarg fields: Limit the data returned to a specific list of fields.
            The default is to retrieve all fields.
            Valid fields are:

                * affiliation
                * alias_enabled
                * bugzilla_email
                * certificate_serial
                * comments
                * country_code
                * creation
                * email
                * emailtoken
                * facsimile
                * gpg_keyid
                * group_roles
                * human_name
                * id
                * internal_comments
                * ircnick
                * last_seen
                * latitude
                * locale
                * longitude
                * memberships
                * old_password
                * password
                * password_changed
                * passwordtoken
                * postal_address
                * privacy
                * roles
                * ssh_key
                * status
                * status_change
                * telephone
                * timezone
                * unverified_email
                * username

            Note that for most users who access this data, many of these
            fields will be set to None due to security or privacy settings.
        :returns: a dict relating the key value to the fields.

        .. versionchanged:: 0.3.21
            Return a Bunch instead of a DictContainer
        .. versionchanged:: 0.3.26
            Fixed to return a list with both people who have signed the CLA
            and have not
        '''
        # Make sure we have a valid key value
        if key not in ('id', 'username', 'email'):
            raise KeyError('key must be one of "id", "username", or'
                           ' "email"')

        if fields:
            fields = list(fields)
            for field in fields:
                if field not in USERFIELDS:
                    raise KeyError('%(field)s is not a valid field to'
                                   ' filter' % {'field': to_bytes(field)})
        else:
            fields = USERFIELDS

        # Make sure we retrieve the key value
        unrequested_fields = []
        if key not in fields:
            unrequested_fields.append(key)
            fields.append(key)
        if 'bugzilla_email' in fields:
            # Need id and email for the bugzilla information
            if 'id' not in fields:
                unrequested_fields.append('id')
                fields.append('id')
            if 'email' not in fields:
                unrequested_fields.append('email')
                fields.append('email')

        request = self.send_request(
            '/user/list',
            req_params={
                'search': search,
                'fields': [f for f in fields if f != 'bugzilla_email']
            },
            auth=True)

        people = Bunch()
        for person in itertools.chain(request['people'],
                                      request['unapproved_people']):
            # Retrieve bugzilla_email from our list if necessary
            if 'bugzilla_email' in fields:
                if person['id'] in self.__bugzilla_email:
                    person['bugzilla_email'] = \
                        self.__bugzilla_email[person['id']]
                else:
                    person['bugzilla_email'] = person['email']

            person_key = person[key]
            # Remove any fields that weren't requested by the user
            if unrequested_fields:
                for field in unrequested_fields:
                    del person[field]

            # Add the person record to the people dict
            people[person_key] = person

        return people

    def people_by_id(self):
        '''*Deprecated* Use people_by_key() instead.

        Returns a dict relating user IDs to human_name, email, username,
        and bugzilla email

        .. versionchanged:: 0.3.21
            Return a Bunch instead of a DictContainer
        '''
        warnings.warn(
            "people_by_id() is deprecated and will be removed in"
            " 0.4.  Please port your code to use people_by_key(key='id',"
            " fields=['human_name', 'email', 'username', 'bugzilla_email'])"
            " instead", DeprecationWarning, stacklevel=2)

        request = self.send_request('/json/user_id', auth=True)
        user_to_id = {}
        people = Bunch()
        for person_id, username in request['people'].items():
            person_id = int(person_id)
            # change userids from string back to integer
            people[person_id] = {'username': username, 'id': person_id}
            user_to_id[username] = person_id

        # Retrieve further useful information about the users
        request = self.send_request('/group/dump', auth=True)
        for user in request['people']:
            userid = user_to_id[user[0]]
            person = people[userid]
            person['email'] = user[1]
            person['human_name'] = user[2]
            if userid in self.__bugzilla_email:
                person['bugzilla_email'] = self.__bugzilla_email[userid]
            else:
                person['bugzilla_email'] = person['email']

        return people

    ### Utils ###

    def people_by_groupname(self, groupname):
        '''Return a list of persons for the given groupname.

        :arg groupname: Name of the group to look up
        :returns: A list of person objects from the group.  If the group
            contains no entries, then an empty list is returned.
        '''
        people = self.people_by_id()
        group = dict(self.group_by_name(groupname))
        userids = [user[u'person_id'] for user in
                   group[u'approved_roles'] + group[u'unapproved_roles']]
        return [people[userid] for userid in userids]

    ### Configs ###

    def get_config(self, username, application, attribute):
        '''Return the config entry for the key values.

        :arg username: Username of the person
        :arg application: Application for which the config is set
        :arg attribute: Attribute key to lookup
        :raises AppError: if the server returns an exception
        :returns: The unicode string that describes the value.  If no entry
            matched the username, application, and attribute then None is
            returned.
        '''
        request = self.send_request('config/list/%s/%s/%s' %
                                    (username, application, attribute),
                                    auth=True)
        if 'exc' in request:
            raise AppError(
                name=request['exc'],
                message=request['tg_flash']
            )

        # Return the value if it exists, else None.
        if 'configs' in request and attribute in request['configs']:
            return request['configs'][attribute]
        return None

    def get_configs_like(self, username, application, pattern=u'*'):
        '''Return the config entries that match the keys and the pattern.

        Note: authentication on the server will prevent anyone but the user
        or a fas admin from viewing or changing their configs.

        :arg username: Username of the person
        :arg application: Application for which the config is set
        :kwarg pattern: A pattern to select values for.  This accepts * as a
            wildcard character. Default='*'
        :raises AppError: if the server returns an exception
        :returns: A dict mapping ``attribute`` to ``value``.
        '''
        request = self.send_request(
            'config/list/%s/%s/%s' %
            (username, application, pattern),
            auth=True)
        if 'exc' in request:
            raise AppError(
                name=request['exc'],
                message=request['tg_flash'])

        return request['configs']

    def set_config(self, username, application, attribute, value):
        '''Set a config entry in FAS for the user.

        Note: authentication on the server will prevent anyone but the user
        or a fas admin from viewing or changing their configs.

        :arg username: Username of the person
        :arg application: Application for which the config is set
        :arg attribute: The name of the config key that we're setting
        :arg value: The value to set this to
        :raises AppError: if the server returns an exception
        '''
        request = self.send_request(
            'config/set/%s/%s/%s' %
            (username, application, attribute),
            req_params={'value': value}, auth=True)

        if 'exc' in request:
            raise AppError(
                name=request['exc'],
                message=request['tg_flash'])

    def people_query(self, constraints=None, columns=None):
        '''Returns a list of dicts representing database rows

        :arg constraints: A dictionary specifying WHERE constraints on columns
        :arg columns: A list of columns to be selected in the query
        :raises AppError: if the query failed on the server (most likely
            because  the server was given a bad query)
        :returns: A list of dicts representing database rows (the keys of
            the dict are the columns requested)

        .. versionadded:: 0.3.12.1
        '''
        if constraints is None:
            constraints = {}
        if columns is None:
            columns = []

        req_params = {}
        req_params.update(constraints)
        req_params['columns'] = ','.join(columns)

        try:
            request = self.send_request(
                'json/people_query',
                req_params=req_params, auth=True)
            if request['success']:
                return request['data']
            else:
                raise AppError(message=request['error'], name='FASError')
        except FedoraServiceError:
            raise

    ### Certs ###

    def user_gencert(self):
        '''Generate a cert for a user'''
        try:
            request = self.send_request('user/dogencert', auth=True)
        except FedoraServiceError:
            raise
        if not request['cla']:
            raise CLAError
        return "%(cert)s\n%(key)s" % request

    ### Passwords ###

    def verify_password(self, username, password):
        '''Return whether the username and password pair are valid.

        :arg username: username to try authenticating
        :arg password: password for the user
        :returns: True if the username/password are valid.  False otherwise.
        '''
        return self.proxy.verify_password(username, password)

    ### fasClient Special Methods ###

    def group_data(self, force_refresh=None):
        '''Return administrators/sponsors/users and group type for all groups

        :arg force_refresh: If true, the returned data will be queried from the
            database, as opposed to memcached.
        :raises AppError: if the query failed on the server
        :returns: A dict mapping group names to the group type and the
            user IDs of the administrator, sponsors, and users of the group.

        .. versionadded:: 0.3.8
        '''
        params = {}
        if force_refresh:
            params['force_refresh'] = True

        try:
            request = self.send_request(
                'json/fas_client/group_data',
                req_params=params, auth=True)
            if request['success']:
                return request['data']
            else:
                raise AppError(
                    message='FAS server unable to retrieve'
                    ' group members', name='FASError')
        except FedoraServiceError:
            raise

    def user_data(self):
        '''Return user data for all users in FAS

        Note: If the user is not authorized to see password hashes,
        '*' is returned for the hash.

        :raises AppError: if the query failed on the server
        :returns: A dict mapping user IDs to a username, password hash,
            SSH public key, email address, and status.

        .. versionadded:: 0.3.8
        '''
        try:
            request = self.send_request('json/fas_client/user_data', auth=True)
            if request['success']:
                return request['data']
            else:
                raise AppError(
                    message='FAS server unable to retrieve user'
                    ' information', name='FASError')
        except FedoraServiceError:
            raise
コード例 #14
0
class AccountSystem(BaseClient):
    '''An object for querying the Fedora Account System.

    The Account System object provides a python API for talking to the Fedora
    Account System.  It abstracts the http requests, cookie handling, and
    other details so you can concentrate on the methods that are important to
    your program.

    .. warning::

        If your code is trying to use the AccountSystem object to
        connect to fas for multiple users you probably want to use
        :class:`~fedora.client.FasProxyClient` instead.  If your code is
        trying to reuse a single instance of AccountSystem for multiple users
        you *definitely* want to use :class:`~fedora.client.FasProxyClient`
        instead.  Using AccountSystem in these cases may result in a user
        being logged in as a different user.  (This may be the case even if
        you instantiate a new AccountSystem object for each user if
        :attr:cache_session: is True since that creates a file on the file
        system that can end up loading session credentials for the wrong
        person.

    .. versionchanged:: 0.3.26
        Added :meth:`~fedora.client.AccountSystem.gravatar_url` that returns
        a url to a gravatar for a user.
    .. versionchanged:: 0.3.33
        Renamed :meth:`~fedora.client.AccountSystem.gravatar_url` to
        :meth:`~fedora.client.AccountSystem.avatar_url`.
    '''
    # proxy is a thread-safe connection to the fas server for verifying
    # passwords of other users
    proxy = None

    # size that we allow to request from remote avatar providers.
    _valid_avatar_sizes = (32, 64, 140)
    # URLs for remote avatar providers.
    _valid_avatar_services = ['libravatar', 'gravatar']

    def __init__(self, base_url='https://admin.fedoraproject.org/accounts/',
                 *args, **kwargs):
        '''Create the AccountSystem client object.

        :kwargs base_url: Base of every URL used to contact the server.
            Defaults to the Fedora Project FAS instance.
        :kwargs useragent: useragent string to use.  If not given, default to
            "Fedora Account System Client/VERSION"
        :kwargs debug: If True, log debug information
        :kwargs username: username for establishing authenticated connections
        :kwargs password: password to use with authenticated connections
        :kwargs session_cookie: **Deprecated** Use session_id instead.
            User's session_cookie to connect to the server
        :kwargs session_id: user's session_id to connect to the server
        :kwargs cache_session: if set to true, cache the user's session cookie
            on the filesystem between runs.
        '''
        if 'useragent' not in kwargs:
            kwargs['useragent'] = \
                'Fedora Account System Client/%s' % __version__

        super(AccountSystem, self).__init__(base_url, *args, **kwargs)
        # We need a single proxy for the class to verify username/passwords
        # against.
        if not self.proxy:
            self.proxy = FasProxyClient(base_url, useragent=self.useragent,
                                        session_as_cookie=False,
                                        debug=self.debug,
                                        insecure=self.insecure)

        # Preseed a list of FAS accounts with bugzilla addresses
        # This allows us to specify a different email for bugzilla than is
        # in the FAS db.  It is a hack, however, until FAS has a field for the
        # bugzilla address.
        self.__bugzilla_email = {
            # Konstantin Ryabitsev: [email protected]
            100029: '*****@*****.**',
            # Sean Reifschneider: [email protected]
            100488: '*****@*****.**',
            # Karen Pease: [email protected]
            100281: '*****@*****.**',
            # Robert Scheck: [email protected]
            100093: '*****@*****.**',
            # Scott Bakers: [email protected]
            100881: '*****@*****.**',
            # Colin Charles: [email protected]
            100014: '*****@*****.**',
            # W. Michael Petullo: [email protected]
            100136: '*****@*****.**',
            # Elliot Lee: [email protected]
            100060: '*****@*****.**',
            # Control Center Team: Bugzilla user but email doesn't exist
            9908: '*****@*****.**',
            # Máirín Duffy
            100548: '*****@*****.**',
            # Muray McAllister: [email protected]
            102321: '*****@*****.**',
            # William Jon McCann: [email protected]
            102489: '*****@*****.**',
            # Matt Domsch's rebuild script -- bz email goes to /dev/null
            103590: '*****@*****.**',
            # Sindre Pedersen Bjørdal: [email protected]
            100460: '*****@*****.**',
            # Jesus M. Rodriguez: [email protected]
            102180: '*****@*****.**',
            # Roozbeh Pournader: [email protected]
            100350: '*****@*****.**',
            # Michael DeHaan: [email protected]
            100603: '*****@*****.**',
            # Sebastian Gosenheimer: [email protected]
            103647: '*****@*****.**',
            # Ben Konrath: [email protected]
            101156: '*****@*****.**',
            # Kai Engert: [email protected]
            100399: '*****@*****.**',
            # William Jon McCann: [email protected]
            102952: '*****@*****.**',
            # Simon Wesp: [email protected]
            109464: '*****@*****.**',
            # Robert M. Albrecht: [email protected]
            101475: '*****@*****.**',
            # Davide Cescato: [email protected]
            123204: '*****@*****.**',
            # Nick Bebout: [email protected]
            101458: '*****@*****.**',
            # Niels Haase: [email protected]
            126862: '*****@*****.**',
            # Thomas Janssen: [email protected]
            103110: '*****@*****.**',
            # Michael J Gruber: '*****@*****.**'
            105113: '*****@*****.**',
            # Juan Manuel Rodriguez Moreno: '*****@*****.**'
            101302: '*****@*****.**',
            # Andrew Cagney: '*****@*****.**'
            102169: '*****@*****.**',
            # Jeremy Katz: '*****@*****.**'
            100036: '*****@*****.**',
            # Dominic Hopf: '*****@*****.**'
            124904: '*****@*****.**',
            # Christoph Wickert: '*****@*****.**':
            100271: '*****@*****.**',
            # Elliott Baron: '*****@*****.**'
            106760: '*****@*****.**',
            # Thomas Spura: '*****@*****.**'
            111433: '*****@*****.**',
            # Adam Miller: '*****@*****.**'
            110673: '*****@*****.**',
            # Garrett Holmstrom: '*****@*****.**'
            131739: '*****@*****.**',
            # Tareq Al Jurf: [email protected]
            109863: '*****@*****.**',
            # Josh Kayse: [email protected]
            148243: '*****@*****.**',
            # Behdad Esfahbod: [email protected]
            100102: '*****@*****.**',
            # Daniel Bruno: [email protected]
            101608: '*****@*****.**',
            # Beth Lynn Eicher: [email protected]
            148706: '*****@*****.**',
            # Andre Robatino: [email protected]
            114970: '*****@*****.**',
            # Jeff Sheltren: [email protected]
            100058: '*****@*****.**',
            # Josh Boyer: [email protected]
            100115: '*****@*****.**',
            # Matthew Miller: [email protected]
            100042: '*****@*****.**',
            # Jamie Nguyen: [email protected]
            160587: '*****@*****.**',
            # Nikos Roussos: [email protected]
            144436: '*****@*****.**',
            # Benedikt Schäfer: [email protected]
            154726: '*****@*****.**',
            # Ricky Elrod: [email protected]
            139137: '*****@*****.**',
            # David Xie: [email protected]
            167133: '*****@*****.**',
            # Felix Schwarz: [email protected]
            103551: '*****@*****.**',
            # Martin Holec: [email protected]
            137561: '*****@*****.**',
            # John Dulaney: [email protected]
            149140: '*****@*****.**',
            # Niels de Vos: [email protected]
            102792: '*****@*****.**',
            # Shawn Wells: [email protected]
            156515: '*****@*****.**',
            # Christopher Tubbs: [email protected]
            160404: '*****@*****.**',
            # Björn Esser: [email protected]
            163460: '*****@*****.**',
            # Amit Shah: [email protected]
            115389: '*****@*****.**',
            # Mark Wielard: [email protected]
            102697: '*****@*****.**',
            # Benjamin Lefoul: [email protected]
            189661: '*****@*****.**',
            # Mike Ruckman: [email protected]
            172063: '*****@*****.**',
            # Dusty Mabe: [email protected]
            170115: '*****@*****.**',
            # Ben Williams: [email protected]
            100572: '*****@*****.**',
            # David Dykstra: [email protected]
            216452: '*****@*****.**',
            # Till Hofmann: <*****@*****.**>
            175557: '*****@*****.**',
        }
        # A few people have an email account that is used in owners.list but
        # have setup a bugzilla account for their primary account system email
        # address now.  Map these here.
        self.__alternate_email = {
            # Damien Durand: [email protected]
            '*****@*****.**': 100406,
            # Kevin Fenzi: [email protected]
            '*****@*****.**': 100037,
        }
        for bugzilla_map in self.__bugzilla_email.items():
            self.__alternate_email[bugzilla_map[1]] = bugzilla_map[0]

        # We use the two mappings as follows::
        # When looking up a user by email, use __alternate_email.
        # When looking up a bugzilla email address use __bugzilla_email.
        #
        # This allows us to parse in owners.list and have a value for all the
        # emails in there while not using the alternate email unless it is
        # the only option.

    # TODO: Use exceptions properly

    ### Set insecure properly ###
    # When setting insecure, we have to set it both on ourselves and on
    # self.proxy
    def _get_insecure(self):
        return self._insecure

    def _set_insecure(self, insecure):
        self._insecure = insecure
        self.proxy = FasProxyClient(self.base_url, useragent=self.useragent,
                                    session_as_cookie=False, debug=self.debug,
                                    insecure=insecure)
        return insecure
    #: If this attribute is set to True, do not check server certificates
    #: against their CA's.  This means that man-in-the-middle attacks are
    #: possible. You might turn this option on for testing against a local
    #: version of a server with a self-signed certificate but it should be off
    #: in production.
    insecure = property(_get_insecure, _set_insecure)

    ### Groups ###

    def create_group(self, name, display_name, owner, group_type,
                     invite_only=0, needs_sponsor=0, user_can_remove=1,
                     prerequisite='', joinmsg='', apply_rules='None'):
        '''Creates a FAS group.

        :arg name: The short group name (alphanumeric only).
        :arg display_name: A longer version of the group's name.
        :arg owner: The username of the FAS account which owns the new group.
        :arg group_type: The kind of group being created. Current valid options
            are git, svn, hg, shell, and tracking.
        :kwarg invite_only: Users must be invited to the group, they cannot
            join on their own.
        :kwarg needs_sponsor: Users must be sponsored into the group.
        :kwarg user_can_remove: Users can remove themselves from the group.
        :kwarg prerequisite: Users must be in the given group (string) before
            they can join the new group.
        :kwarg joinmsg: A message shown to users when they apply to the group.
        :kwarg apply_rules: Rules for applying to the group, shown to users
            before they apply.
        :rtype: :obj:`bunch.Bunch`
        :returns: A Bunch containing information about the group that was
            created.

        .. versionadded:: 0.3.29
        '''
        req_params = {
            'invite_only': invite_only,
            'needs_sponsor': needs_sponsor,
            'user_can_remove': user_can_remove,
            'prerequisite': prerequisite,
            'joinmsg': joinmsg,
            'apply_rules': apply_rules
        }

        request = self.send_request(
            '/group/create/%s/%s/%s/%s' % (
                urllib.quote(name),
                urllib.quote(display_name),
                urllib.quote(owner),
                urllib.quote(group_type)),
            req_params=req_params,
            auth=True
        )
        return request

    def group_by_id(self, group_id):
        '''Returns a group object based on its id'''
        params = {'group_id': int(group_id)}
        request = self.send_request(
            'json/group_by_id',
            auth=True,
            req_params=params
        )
        if request['success']:
            return request['group']
        else:
            return dict()

    def group_by_name(self, groupname):
        '''Returns a group object based on its name'''
        params = {'groupname': groupname}
        request = self.send_request(
            'json/group_by_name',
            auth=True,
            req_params=params
        )
        if request['success']:
            return request['group']
        else:
            raise AppError(
                message='FAS server unable to retrieve group'
                ' %(group)s' % {'group': to_bytes(groupname)},
                name='FASError')

    def group_members(self, groupname):
        '''Return a list of people approved for a group.

        This method returns a list of people who are in the requested group.
        The people are all approved in the group.  Unapproved people are not
        shown.  The format of data is::

            \[{'username': '******', 'role_type': 'user'},
            \{'username': '******', 'role_type': 'sponsor'}]

        role_type can be one of 'user', 'sponsor', or 'administrator'.

        .. versionadded:: 0.3.2
        .. versionchanged:: 0.3.21
            Return a Bunch instead of a DictContainer
        '''
        request = self.send_request('/group/dump/%s' %
                                    urllib.quote(groupname), auth=True)

        return [Bunch(username=user[0],
                      role_type=user[3]) for user in request['people']]

    ### People ###

    def person_by_id(self, person_id):
        '''Returns a person object based on its id'''
        person_id = int(person_id)
        params = {'person_id': person_id}
        request = self.send_request('json/person_by_id', auth=True,
                                    req_params=params)

        if request['success']:
            if person_id in self.__bugzilla_email:
                request['person']['bugzilla_email'] = \
                    self.__bugzilla_email[person_id]
            else:
                request['person']['bugzilla_email'] = \
                    request['person']['email']

            # In a devel version of FAS, membership info was returned
            # separately
            # This was later corrected (can remove this code at some point)
            if 'approved' in request:
                request['person']['approved_memberships'] = request['approved']
            if 'unapproved' in request:
                request['person']['unapproved_memberships'] = \
                    request['unapproved']
            return request['person']
        else:
            return dict()

    def person_by_username(self, username):
        '''Returns a person object based on its username'''
        params = {'username': username}
        request = self.send_request(
            'json/person_by_username',
            auth=True,
            req_params=params)

        if request['success']:
            person = request['person']
            if person['id'] in self.__bugzilla_email:
                person['bugzilla_email'] = self.__bugzilla_email[person['id']]
            else:
                person['bugzilla_email'] = person['email']
            # In a devel version of FAS, membership info was returned
            # separately
            # This was later corrected (can remove this code at some point)
            if 'approved' in request:
                request['person']['approved_memberships'] = request['approved']
            if 'unapproved' in request:
                request['person']['unapproved_memberships'] = \
                    request['unapproved']
            return person
        else:
            return dict()

    def avatar_url(self, username, size=64,
                   default=None, lookup_email=True,
                   service=None):
        ''' Returns a URL to an avatar for a given username.

        Avatars are drawn from third party services.

        :arg username: FAS username to construct a avatar url for
        :kwarg size: size of the avatar.  Allowed sizes are 32, 64, 140.
            Default: 64
        :kwarg default: If the service does not have a avatar image for the
            email address, this url is returned instead.  Default:
            the fedora logo at the specified size.
        :kwarg lookup_email:  If true, use the email from FAS for gravatar.com
            lookups, otherwise just append @fedoraproject.org to the username.
            For libravatar.org lookups, this is ignored.  The openid identifier
            of the user is used instead.
            Note that gravatar.com lookups will be much slower if lookup_email
            is set to True since we'd have to make a query against FAS itself.
        :kwarg service: One of 'libravatar' or 'gravatar'.
            Default: 'libravatar'.
        :raises ValueError: if the size parameter is not allowed or if the
            service is not one of 'libravatar' or 'gravatar'
        :rtype: :obj:`str`
        :returns: url of a avatar for the user

        If that user has no avatar entry, instruct the remote service to
        redirect us to the Fedora logo.

        If that user has no email attribute, then make a fake request to
        the third party service.

        .. versionadded:: 0.3.26
        .. versionchanged: 0.3.30
            Add lookup_email parameter to control whether we generate avatar
            urls with the email in fas or [email protected]
        .. versionchanged: 0.3.33
            Renamed from `gravatar_url` to `avatar_url`
        .. versionchanged: 0.3.34
            Updated libravatar to use the user's openid identifier.
        '''

        if size not in self._valid_avatar_sizes:
            raise ValueError(
                'Size %(size)i disallowed.  Must be in %(valid_sizes)r' % {
                    'size': size,
                    'valid_sizes': self._valid_avatar_sizes
                }
            )

        # If our caller explicitly requested libravatar but they don't have
        # it installed, then we need to raise a nice error and let them know.
        if service == 'libravatar' and not libravatar:
            raise ValueError("Install python-pylibravatar if you want to "
                             "use libravatar as an avatar provider.")

        # If our caller didn't specify a service, let's pick a one for them.
        # If they have pylibravatar installed, then by all means let freedom
        # ring!  Otherwise, we'll use gravatar.com if we have to.
        if not service:
            if libravatar:
                service = 'libravatar'
            else:
                service = 'gravatar'

        # Just double check to make sure they didn't pass us a bogus service.
        if service not in self._valid_avatar_services:
            raise ValueError(
                'Service %(service)r disallowed. '
                'Must be in %(valid_services)r' % {
                    'service': service,
                    'valid_services': self._valid_avatar_services
                }
            )

        if not default:
            default = "http://fedoraproject.org/static/images/" + \
                      "fedora_infinity_%ix%i.png" % (size, size)

        if service == 'libravatar':
            openid = 'http://%s.id.fedoraproject.org/' % username
            return libravatar.libravatar_url(
                openid=openid,
                size=size,
                default=default,
            )
        else:
            if lookup_email:
                person = self.person_by_username(username)
                email = person.get('email', 'no_email')
            else:
                email = "*****@*****.**" % username

            query_string = urllib.urlencode({
                's': size,
                'd': default,
            })

            hash = md5(email).hexdigest()

            return "http://www.gravatar.com/avatar/%s?%s" % (
                hash, query_string)

    def gravatar_url(self, *args, **kwargs):
        """ *Deprecated* - Use avatar_url.

         .. versionadded:: 0.3.26
         .. versionchanged: 0.3.30
            Add lookup_email parameter to control whether we generate gravatar
            urls with the email in fas or [email protected]
         .. versionchanged: 0.3.33
            Deprecated in favor of `avatar_url`.
        """

        warnings.warn(
            "gravatar_url is deprecated and will be removed in"
            " a future version.  Please port your code to use avatar_url(...,"
            " service='libravatar', ...)  instead",
            DeprecationWarning, stacklevel=2)

        if 'service' in kwargs:
            raise TypeError("'service' is an invalid keyword argument for"
                            " this function.  Use avatar_url() instead)")

        return self.avatar_url(*args, service='gravatar', **kwargs)

    def user_id(self):
        '''Returns a dict relating user IDs to usernames'''
        request = self.send_request('json/user_id', auth=True)
        people = {}
        for person_id, username in request['people'].items():
            # change userids from string back to integer
            people[int(person_id)] = username
        return people

    def people_by_key(self, key=u'username', search=u'*', fields=None):
        '''Return a dict of people

        :kwarg key: Key by this field.  Valid values are 'id', 'username', or
            'email'.  Default is 'username'
        :kwarg search: Pattern to match usernames against.  Defaults to the
            '*' wildcard which matches everyone.
        :kwarg fields: Limit the data returned to a specific list of fields.
            The default is to retrieve all fields.
            Valid fields are:

                * affiliation
                * alias_enabled
                * bugzilla_email
                * certificate_serial
                * comments
                * country_code
                * creation
                * email
                * emailtoken
                * facsimile
                * gpg_keyid
                * group_roles
                * human_name
                * id
                * internal_comments
                * ircnick
                * last_seen
                * latitude
                * locale
                * longitude
                * memberships
                * old_password
                * password
                * password_changed
                * passwordtoken
                * postal_address
                * privacy
                * roles
                * ssh_key
                * status
                * status_change
                * telephone
                * timezone
                * unverified_email
                * username

            Note that for most users who access this data, many of these
            fields will be set to None due to security or privacy settings.
        :returns: a dict relating the key value to the fields.

        .. versionchanged:: 0.3.21
            Return a Bunch instead of a DictContainer
        .. versionchanged:: 0.3.26
            Fixed to return a list with both people who have signed the CLA
            and have not
        '''
        # Make sure we have a valid key value
        if key not in ('id', 'username', 'email'):
            raise KeyError('key must be one of "id", "username", or'
                           ' "email"')

        if fields:
            fields = list(fields)
            for field in fields:
                if field not in USERFIELDS:
                    raise KeyError('%(field)s is not a valid field to'
                                   ' filter' % {'field': to_bytes(field)})
        else:
            fields = USERFIELDS

        # Make sure we retrieve the key value
        unrequested_fields = []
        if key not in fields:
            unrequested_fields.append(key)
            fields.append(key)
        if 'bugzilla_email' in fields:
            # Need id and email for the bugzilla information
            if 'id' not in fields:
                unrequested_fields.append('id')
                fields.append('id')
            if 'email' not in fields:
                unrequested_fields.append('email')
                fields.append('email')

        request = self.send_request(
            '/user/list',
            req_params={
                'search': search,
                'fields': [f for f in fields if f != 'bugzilla_email']
            },
            auth=True)

        people = Bunch()
        for person in itertools.chain(request['people'],
                                      request['unapproved_people']):
            # Retrieve bugzilla_email from our list if necessary
            if 'bugzilla_email' in fields:
                if person['id'] in self.__bugzilla_email:
                    person['bugzilla_email'] = \
                        self.__bugzilla_email[person['id']]
                else:
                    person['bugzilla_email'] = person['email']

            person_key = person[key]
            # Remove any fields that weren't requested by the user
            if unrequested_fields:
                for field in unrequested_fields:
                    del person[field]

            # Add the person record to the people dict
            people[person_key] = person

        return people

    def people_by_id(self):
        '''*Deprecated* Use people_by_key() instead.

        Returns a dict relating user IDs to human_name, email, username,
        and bugzilla email

        .. versionchanged:: 0.3.21
            Return a Bunch instead of a DictContainer
        '''
        warnings.warn(
            "people_by_id() is deprecated and will be removed in"
            " 0.4.  Please port your code to use people_by_key(key='id',"
            " fields=['human_name', 'email', 'username', 'bugzilla_email'])"
            " instead", DeprecationWarning, stacklevel=2)

        request = self.send_request('/json/user_id', auth=True)
        user_to_id = {}
        people = Bunch()
        for person_id, username in request['people'].items():
            person_id = int(person_id)
            # change userids from string back to integer
            people[person_id] = {'username': username, 'id': person_id}
            user_to_id[username] = person_id

        # Retrieve further useful information about the users
        request = self.send_request('/group/dump', auth=True)
        for user in request['people']:
            userid = user_to_id[user[0]]
            person = people[userid]
            person['email'] = user[1]
            person['human_name'] = user[2]
            if userid in self.__bugzilla_email:
                person['bugzilla_email'] = self.__bugzilla_email[userid]
            else:
                person['bugzilla_email'] = person['email']

        return people

    ### Utils ###

    def people_by_groupname(self, groupname):
        '''Return a list of persons for the given groupname.

        :arg groupname: Name of the group to look up
        :returns: A list of person objects from the group.  If the group
            contains no entries, then an empty list is returned.
        '''
        people = self.people_by_id()
        group = dict(self.group_by_name(groupname))
        userids = [user[u'person_id'] for user in
                   group[u'approved_roles'] + group[u'unapproved_roles']]
        return [people[userid] for userid in userids]

    ### Configs ###

    def get_config(self, username, application, attribute):
        '''Return the config entry for the key values.

        :arg username: Username of the person
        :arg application: Application for which the config is set
        :arg attribute: Attribute key to lookup
        :raises AppError: if the server returns an exception
        :returns: The unicode string that describes the value.  If no entry
            matched the username, application, and attribute then None is
            returned.
        '''
        request = self.send_request('config/list/%s/%s/%s' %
                                    (username, application, attribute),
                                    auth=True)
        if 'exc' in request:
            raise AppError(
                name=request['exc'],
                message=request['tg_flash']
            )

        # Return the value if it exists, else None.
        if 'configs' in request and attribute in request['configs']:
            return request['configs'][attribute]
        return None

    def get_configs_like(self, username, application, pattern=u'*'):
        '''Return the config entries that match the keys and the pattern.

        Note: authentication on the server will prevent anyone but the user
        or a fas admin from viewing or changing their configs.

        :arg username: Username of the person
        :arg application: Application for which the config is set
        :kwarg pattern: A pattern to select values for.  This accepts * as a
            wildcard character. Default='*'
        :raises AppError: if the server returns an exception
        :returns: A dict mapping ``attribute`` to ``value``.
        '''
        request = self.send_request(
            'config/list/%s/%s/%s' %
            (username, application, pattern),
            auth=True)
        if 'exc' in request:
            raise AppError(
                name=request['exc'],
                message=request['tg_flash'])

        return request['configs']

    def set_config(self, username, application, attribute, value):
        '''Set a config entry in FAS for the user.

        Note: authentication on the server will prevent anyone but the user
        or a fas admin from viewing or changing their configs.

        :arg username: Username of the person
        :arg application: Application for which the config is set
        :arg attribute: The name of the config key that we're setting
        :arg value: The value to set this to
        :raises AppError: if the server returns an exception
        '''
        request = self.send_request(
            'config/set/%s/%s/%s' %
            (username, application, attribute),
            req_params={'value': value}, auth=True)

        if 'exc' in request:
            raise AppError(
                name=request['exc'],
                message=request['tg_flash'])

    def people_query(self, constraints=None, columns=None):
        '''Returns a list of dicts representing database rows

        :arg constraints: A dictionary specifying WHERE constraints on columns
        :arg columns: A list of columns to be selected in the query
        :raises AppError: if the query failed on the server (most likely
            because  the server was given a bad query)
        :returns: A list of dicts representing database rows (the keys of
            the dict are the columns requested)

        .. versionadded:: 0.3.12.1
        '''
        if constraints is None:
            constraints = {}
        if columns is None:
            columns = []

        req_params = {}
        req_params.update(constraints)
        req_params['columns'] = ','.join(columns)

        try:
            request = self.send_request(
                'json/people_query',
                req_params=req_params, auth=True)
            if request['success']:
                return request['data']
            else:
                raise AppError(message=request['error'], name='FASError')
        except FedoraServiceError:
            raise

    ### Certs ###

    def user_gencert(self):
        '''Generate a cert for a user'''
        try:
            request = self.send_request('user/dogencert', auth=True)
        except FedoraServiceError:
            raise
        if not request['cla']:
            raise CLAError
        return "%(cert)s\n%(key)s" % request

    ### Passwords ###

    def verify_password(self, username, password):
        '''Return whether the username and password pair are valid.

        :arg username: username to try authenticating
        :arg password: password for the user
        :returns: True if the username/password are valid.  False otherwise.
        '''
        return self.proxy.verify_password(username, password)

    ### fasClient Special Methods ###

    def group_data(self, force_refresh=None):
        '''Return administrators/sponsors/users and group type for all groups

        :arg force_refresh: If true, the returned data will be queried from the
            database, as opposed to memcached.
        :raises AppError: if the query failed on the server
        :returns: A dict mapping group names to the group type and the
            user IDs of the administrator, sponsors, and users of the group.

        .. versionadded:: 0.3.8
        '''
        params = {}
        if force_refresh:
            params['force_refresh'] = True

        try:
            request = self.send_request(
                'json/fas_client/group_data',
                req_params=params, auth=True)
            if request['success']:
                return request['data']
            else:
                raise AppError(
                    message='FAS server unable to retrieve'
                    ' group members', name='FASError')
        except FedoraServiceError:
            raise

    def user_data(self):
        '''Return user data for all users in FAS

        Note: If the user is not authorized to see password hashes,
        '*' is returned for the hash.

        :raises AppError: if the query failed on the server
        :returns: A dict mapping user IDs to a username, password hash,
            SSH public key, email address, and status.

        .. versionadded:: 0.3.8
        '''
        try:
            request = self.send_request('json/fas_client/user_data', auth=True)
            if request['success']:
                return request['data']
            else:
                raise AppError(
                    message='FAS server unable to retrieve user'
                    ' information', name='FASError')
        except FedoraServiceError:
            raise
コード例 #15
0
ファイル: fas2.py プロジェクト: davidhrbac/cnucnu
class AccountSystem(BaseClient):
    '''An object for querying the Fedora Account System.

    The Account System object provides a python API for talking to the Fedora
    Account System.  It abstracts the http requests, cookie handling, and
    other details so you can concentrate on the methods that are important to
    your program.
    '''
    proxy = None
    def __init__(self, base_url='https://admin.fedoraproject.org/accounts/',
            *args, **kwargs):
        '''Create the AccountSystem client object.

        :kwargs base_url: Base of every URL used to contact the server.
            Defaults to the Fedora Project FAS instance.
        :kwargs useragent: useragent string to use.  If not given, default to
            "Fedora Account System Client/VERSION"
        :kwargs debug: If True, log debug information
        :kwargs username: username for establishing authenticated connections
        :kwargs password: password to use with authenticated connections
        :kwargs session_cookie: **Deprecated** Use session_id instead.
            User's session_cookie to connect to the server
        :kwargs session_id: user's session_id to connect to the server
        :kwargs cache_session: if set to true, cache the user's session cookie
            on the filesystem between runs.
        '''
        if 'useragent' not in kwargs:
            kwargs['useragent'] = 'Fedora Account System Client/%s' \
                    % __version__
        super(AccountSystem, self).__init__(base_url, *args, **kwargs)
        # We need a single proxy for the class to verify username/passwords
        # against.
        if not self.proxy:
            self.proxy = FasProxyClient(base_url, useragent=self.useragent,
                    session_as_cookie=False, debug=self.debug)

        # Preseed a list of FAS accounts with bugzilla addresses
        # This allows us to specify a different email for bugzilla than is
        # in the FAS db.  It is a hack, however, until FAS has a field for the
        # bugzilla address.
        self.__bugzilla_email = {
                # Konstantin Ryabitsev: [email protected]
                100029: '*****@*****.**',
                # Sean Reifschneider: [email protected]
                100488: '*****@*****.**',
                # Karen Pease: [email protected]
                100281: '*****@*****.**',
                # Robert Scheck: [email protected]
                100093: '*****@*****.**',
                # Scott Bakers: [email protected]
                100881: '*****@*****.**',
                # Colin Charles: [email protected]
                100014: '*****@*****.**',
                # W. Michael Petullo: [email protected]
                100136: '*****@*****.**',
                # Elliot Lee: [email protected]
                100060: '*****@*****.**',
                # Control Center Team: Bugzilla user but email doesn't exist
                9908: '*****@*****.**',
                # Máirín Duffy
                100548: '*****@*****.**',
                # Muray McAllister: [email protected]
                102321: '*****@*****.**',
                # William Jon McCann: [email protected]
                102489: '*****@*****.**',
                # Matt Domsch's rebuild script -- bz email goes to /dev/null
                103590: '*****@*****.**',
                # Sindre Pedersen Bjørdal: [email protected]
                100460 : '*****@*****.**',
                # Jesus M. Rodriguez: [email protected]
                102180: '*****@*****.**',
                # Jeff Sheltren: [email protected]
                100058: '*****@*****.**',
                # Roozbeh Pournader: [email protected]
                100350: '*****@*****.**',
                # Michael DeHaan: [email protected]
                100603: '*****@*****.**',
                # Sebastian Gosenheimer: [email protected]
                103647: '*****@*****.**',
                # Ben Konrath: [email protected]
                101156: '*****@*****.**',
                # Kai Engert: [email protected]
                100399: '*****@*****.**',
                # William Jon McCann: [email protected]
                102952: '*****@*****.**',
                # Simon Wesp: [email protected]
                109464: '*****@*****.**',
                # Robert M. Albrecht: [email protected]
                101475: '*****@*****.**',
                # Mathieu Bridon: [email protected]
                100753: '*****@*****.**',
                # Davide Cescato: [email protected]
                123204: '*****@*****.**',
                # Nick Bebout: [email protected]
                101458: '*****@*****.**',
                # Niels Haase: [email protected]
                126862: '*****@*****.**',
                # Thomas Janssen: [email protected]
                103110: '*****@*****.**',
                # Michael J Gruber: '*****@*****.**'
                105113: '*****@*****.**',
                # Juan Manuel Rodriguez Moreno: '*****@*****.**'
                101302: '*****@*****.**',
                # Andrew Cagney: '*****@*****.**'
                102169: '*****@*****.**',
                # Jeremy Katz: '*****@*****.**'
                100036: '*****@*****.**',
                # Dominic Hopf: '*****@*****.**'
                124904: '*****@*****.**',
                # Christoph Wickert: '*****@*****.**':
                100271: '*****@*****.**',
                # Elliott Baron: '*****@*****.**'
                106760: '*****@*****.**',
                # Thomas Spura: '*****@*****.**'
                111433: '*****@*****.**',
                # Adam Miller: '*****@*****.**'
                110673: '*****@*****.**',
                # Garrett Holmstrom: '*****@*****.**'
                131739: '*****@*****.**',
                # Tareq Al Jurf: [email protected]
                109863: '*****@*****.**',
                }
        # A few people have an email account that is used in owners.list but
        # have setup a bugzilla account for their primary account system email
        # address now.  Map these here.
        self.__alternate_email = {
                # Damien Durand: [email protected]
                '*****@*****.**': 100406,
                # Kevin Fenzi: [email protected]
                '*****@*****.**': 100037,
                }
        for bugzilla_map in self.__bugzilla_email.items():
            self.__alternate_email[bugzilla_map[1]] = bugzilla_map[0]

        # We use the two mappings as follows::
        # When looking up a user by email, use __alternate_email.
        # When looking up a bugzilla email address use __bugzilla_email.
        #
        # This allows us to parse in owners.list and have a value for all the
        # emails in there while not using the alternate email unless it is
        # the only option.

    # TODO: Use exceptions properly

    ### Groups ###

    def group_by_id(self, group_id):
        '''Returns a group object based on its id'''
        params = {'group_id': int(group_id)}
        request = self.send_request('json/group_by_id', auth = True,
                req_params = params)
        if request['success']:
            return request['group']
        else:
            return dict()

    def group_by_name(self, groupname):
        '''Returns a group object based on its name'''
        params = {'groupname': groupname}
        request = self.send_request('json/group_by_name', auth = True,
                req_params = params)
        if request['success']:
            return request['group']
        else:
            raise AppError(message=_('FAS server unable to retrieve group %s')
                    % groupname, name='FASError')

    def group_members(self, groupname):
        '''Return a list of people approved for a group.

        This method returns a list of people who are in the requested group.
        The people are all approved in the group.  Unapproved people are not
        shown.  The format of data is::

            \[{'username': '******', 'role_type': 'user'},
            \{'username': '******', 'role_type': 'sponsor'}]

        role_type can be one of 'user', 'sponsor', or 'administrator'.

        .. versionadded:: 0.3.2
        '''
        request = self.send_request('/group/dump/%s' %
                urllib.quote(groupname), auth=True)

        return [DictContainer(username=user[0], role_type=user[3])
                    for user in request['people']]

    ### People ###

    def person_by_id(self, person_id):
        '''Returns a person object based on its id'''
        person_id = int(person_id)
        params = {'person_id': person_id}
        request = self.send_request('json/person_by_id', auth=True,
                req_params=params)

        if request['success']:
            if person_id in self.__bugzilla_email:
                request['person']['bugzilla_email'] = \
                        self.__bugzilla_email[person_id]
            else:
                request['person']['bugzilla_email'] = request['person']['email']
            # In the new FAS, membership info is returned separately
            if 'approved' in request:
                request['person']['approved_memberships'] = request['approved']
            if 'unapproved' in request:
                request['person']['unapproved_memberships'] = \
                        request['unapproved']
            return request['person']
        else:
            return dict()

    def person_by_username(self, username):
        '''Returns a person object based on its username'''
        params = {'username': username}
        request = self.send_request('json/person_by_username', auth = True,
                req_params = params)

        if request['success']:
            person = request['person']
            if person['id'] in self.__bugzilla_email:
                person['bugzilla_email'] = self.__bugzilla_email[person['id']]
            else:
                person['bugzilla_email'] = person['email']
            # In the new FAS, membership info is returned separately
            if 'approved' in request:
                request['person']['approved_memberships'] = request['approved']
            if 'unapproved' in request:
                request['person']['unapproved_memberships'] = \
                        request['unapproved']
            return person
        else:
            return dict()

    def user_id(self):
        '''Returns a dict relating user IDs to usernames'''
        request = self.send_request('json/user_id', auth=True)
        people = {}
        for person_id, username in request['people'].items():
            # change userids from string back to integer
            people[int(person_id)] = username
        return people

    def people_by_key(self, key=u'username', search=u'*', fields=None):
        '''Return a dict of people

        :kwarg key: Key by this field.  Valid values are 'id', 'username', or
            'email'.  Default is 'username'
        :kwarg search: Pattern to match usernames against.  Defaults to the
            '*' wildcard which matches everyone.
        :kwarg fields: Limit the data returned to a specific list of fields.
            The default is to retrieve all fields.
            Valid fields are:

                * username
                * certificate_serial
                * locale
                * creation
                * telephone
                * status_change
                * id
                * password_changed
                * privacy
                * comments
                * latitude
                * email
                * status
                * gpg_keyid
                * internal_comments
                * postal_address
                * unverified_email
                * ssh_key
                * passwordtoken
                * ircnick
                * password
                * emailtoken
                * longitude
                * facsimile
                * human_name
                * last_seen
                * bugzilla_email

            Note that for most users who access this data, many of these
            fields will be set to None due to security or privacy settings.
        :returns: a dict relating the key value to the fields.
        '''
        # Make sure we have a valid key value
        if key not in ('id', 'username', 'email'):
            raise KeyError(_('key must be one of "id", "username", or "email"'))

        if fields:
            fields = list(fields)
            for field in fields:
                if field not in USERFIELDS:
                    raise KeyError(_('%(field)s is not a valid field to filter')
                            % {'field': field})
        else:
            fields = USERFIELDS

        # Make sure we retrieve the key value
        unrequested_fields = []
        if key not in fields:
            unrequested_fields.append(key)
            fields.append(key)
        if 'bugzilla_email' in fields:
            # Need id and email for the bugzilla information
            if 'id' not in fields:
                unrequested_fields.append('id')
                fields.append('id')
            if 'email' not in fields:
                unrequested_fields.append('email')
                fields.append('email')

        request = self.send_request('/user/list', req_params={'search': search,
            'fields': [f for f in fields if f != 'bugzilla_email']}, auth=True)

        people = DictContainer()
        for person in request['people']:
            # Retrieve bugzilla_email from our list if necessary
            if 'bugzilla_email' in fields:
                if person['id'] in self.__bugzilla_email:
                    person['bugzilla_email'] = \
                            self.__bugzilla_email[person['id']]
                else:
                    person['bugzilla_email'] = person['email']

            person_key = person[key]
            # Remove any fields that weren't requested by the user
            if unrequested_fields:
                for field in unrequested_fields:
                    del person[field]

            # Add the person record to the people dict
            people[person_key] = person

        return people

    def people_by_id(self):
        '''*Deprecated* Use people_by_key() instead.

        Returns a dict relating user IDs to human_name, email, username,
        and bugzilla email
        '''
        warnings.warn(_("people_by_id() is deprecated and will be removed in"
            " 0.4.  Please port your code to use people_by_key(key='id',"
            " fields=['human_name', 'email', 'username', 'bugzilla_email'])"
            " instead"), DeprecationWarning, stacklevel=2)

        request = self.send_request('/json/user_id', auth=True)
        user_to_id = {}
        people = DictContainer()
        for person_id, username in request['people'].items():
            person_id = int(person_id)
            # change userids from string back to integer
            people[person_id] = {'username': username, 'id': person_id}
            user_to_id[username] = person_id

        # Retrieve further useful information about the users
        request = self.send_request('/group/dump', auth=True)
        for user in request['people']:
            userid = user_to_id[user[0]]
            person = people[userid]
            person['email'] = user[1]
            person['human_name'] = user[2]
            if userid in self.__bugzilla_email:
                person['bugzilla_email'] = self.__bugzilla_email[userid]
            else:
                person['bugzilla_email'] = person['email']

        return people

    ### Utils ###

    def people_by_groupname(self, groupname):
        '''Return a list of persons for the given groupname.

        :arg groupname: Name of the group to look up
        :returns: A list of person objects from the group.  If the group
            contains no entries, then an empty list is returned.
        '''
        people = self.people_by_id()
        group = dict(self.group_by_name(groupname))
        userids = [user[u'person_id'] for user in
                   group[u'approved_roles'] + group[u'unapproved_roles']]
        return [people[userid] for userid in userids]

    ### Configs ###

    def get_config(self, username, application, attribute):
        '''Return the config entry for the key values.

        :arg username: Username of the person
        :arg application: Application for which the config is set
        :arg attribute: Attribute key to lookup
        :raises AppError: if the server returns an exception
        :returns: The unicode string that describes the value.  If no entry
            matched the username, application, and attribute then None is
            returned.
        '''
        request = self.send_request('config/list/%s/%s/%s' %
                (username, application, attribute), auth=True)
        if 'exc' in request:
            raise AppError(name = request['exc'], message = request['tg_flash'])

        # Return the value if it exists, else None.
        if 'configs' in request and attribute in request['configs']:
            return request['configs'][attribute]
        return None

    def get_configs_like(self, username, application, pattern=u'*'):
        '''Return the config entries that match the keys and the pattern.

        Note: authentication on the server will prevent anyone but the user
        or a fas admin from viewing or changing their configs.

        :arg username: Username of the person
        :arg application: Application for which the config is set
        :kwarg pattern: A pattern to select values for.  This accepts * as a
            wildcard character. Default='*'
        :raises AppError: if the server returns an exception
        :returns: A dict mapping ``attribute`` to ``value``.
        '''
        request = self.send_request('config/list/%s/%s/%s' %
                (username, application, pattern), auth=True)
        if 'exc' in request:
            raise AppError(name = request['exc'], message = request['tg_flash'])

        return request['configs']

    def set_config(self, username, application, attribute, value):
        '''Set a config entry in FAS for the user.

        Note: authentication on the server will prevent anyone but the user
        or a fas admin from viewing or changing their configs.

        :arg username: Username of the person
        :arg application: Application for which the config is set
        :arg attribute: The name of the config key that we're setting
        :arg value: The value to set this to
        :raises AppError: if the server returns an exception
        '''
        request = self.send_request('config/set/%s/%s/%s' %
                (username, application, attribute),
                req_params={'value': value}, auth=True)

        if 'exc' in request:
            raise AppError(name = request['exc'], message = request['tg_flash'])

    def people_query(self, constraints=None, columns=None):
        '''Returns a list of dicts representing database rows

        :arg constraints: A dictionary specifying WHERE constraints on columns
        :arg columns: A list of columns to be selected in the query
        :raises AppError: if the query failed on the server (most likely
            because  the server was given a bad query)
        :returns: A list of dicts representing database rows (the keys of
            the dict are the columns requested)

        .. versionadded:: 0.3.12.1
        '''
        if constraints is None:
            constraints = {}
        if columns is None:
            columns = []

        req_params = {}
        req_params.update(constraints)
        req_params['columns'] = ','.join(columns)

        try:
            request = self.send_request('json/people_query',
                req_params=req_params, auth=True)
            if request['success']:
                return request['data']
            else:
                raise AppError(message=request['error'], name='FASError')
        except FedoraServiceError:
            raise

    ### Certs ###

    def user_gencert(self):
        '''Generate a cert for a user'''
        try:
            request = self.send_request('user/dogencert', auth=True)
        except FedoraServiceError:
            raise
        if not request['cla']:
            raise CLAError
        return "%(cert)s\n%(key)s" % request

    ### Passwords ###

    def verify_password(self, username, password):
        '''Return whether the username and password pair are valid.

        :arg username: username to try authenticating
        :arg password: password for the user
        :returns: True if the username/password are valid.  False otherwise.
        '''
        return self.proxy.verify_password(username, password)

    ### fasClient Special Methods ###

    def group_data(self, force_refresh=None):
        '''Return administrators/sponsors/users and group type for all groups

        :arg force_refresh: If true, the returned data will be queried from the
            database, as opposed to memcached.
        :raises AppError: if the query failed on the server
        :returns: A dict mapping group names to the group type and the
            user IDs of the administrator, sponsors, and users of the group.

        .. versionadded:: 0.3.8
        '''
        params = {}
        if force_refresh:
            params['force_refresh'] = True

        try:
            request = self.send_request('json/fas_client/group_data',
                req_params=params, auth=True)
            if request['success']:
                return request['data']
            else:
                raise AppError(message=_('FAS server unable to retrieve group'
                    ' members'), name='FASError')
        except FedoraServiceError:
            raise

    def user_data(self):
        '''Return user data for all users in FAS

        Note: If the user is not authorized to see password hashes,
        '*' is returned for the hash.

        :raises AppError: if the query failed on the server
        :returns: A dict mapping user IDs to a username, password hash,
            SSH public key, email address, and status.

        .. versionadded:: 0.3.8
        '''
        try:
            request = self.send_request('json/fas_client/user_data', auth=True)
            if request['success']:
                return request['data']
            else:
                raise AppError(message=_('FAS server unable to retrieve user'
                    ' information'), name='FASError')
        except FedoraServiceError:
            raise
コード例 #16
0
ファイル: views.py プロジェクト: mairin/fedora-openhw2012
def login(username, password):
    fas = FasProxyClient()
    user = fas.get_user_info({'username': username, 'password': password})[1]
    roles = [g.name for g in user['approved_memberships']]
    return user, roles
コード例 #17
0
class JsonFasVisitManager(BaseVisitManager):
    '''
    This proxies visit requests to the Account System Server running remotely.
    '''
    fas_url = config.get('fas.url', 'https://admin.fedoraproject.org/accounts')
    fas = None

    def __init__(self, timeout):
        self.debug = config.get('jsonfas.debug', False)
        if not self.fas:
            self.fas = FasProxyClient(self.fas_url, debug=self.debug,
                    session_name=config.get('visit.cookie.name', 'tg-visit'),
                    useragent='JsonFasVisitManager/%s' % __version__)
        BaseVisitManager.__init__(self, timeout)
        log.debug('JsonFasVisitManager.__init__: exit')

    def create_model(self):
        '''
        Create the Visit table if it doesn't already exist.

        Not needed as the visit tables reside remotely in the FAS2 database.
        '''
        pass

    def new_visit_with_key(self, visit_key):
        '''
        Return a new Visit object with the given key.
        '''
        log.debug('JsonFasVisitManager.new_visit_with_key: enter')
        # Hit any URL in fas2 with the visit_key set.  That will call the
        # new_visit method in fas2
        # We only need to get the session cookie from this request
        request_data = self.fas.refresh_session(visit_key)
        session_id = request_data[0]
        log.debug('JsonFasVisitManager.new_visit_with_key: exit')
        return Visit(session_id, True)

    def visit_for_key(self, visit_key):
        '''
        Return the visit for this key or None if the visit doesn't exist or has
        expired.
        '''
        log.debug('JsonFasVisitManager.visit_for_key: enter')
        # Hit any URL in fas2 with the visit_key set.  That will call the
        # new_visit method in fas2
        # We only need to get the session cookie from this request
        request_data = self.fas.refresh_session(visit_key)
        session_id = request_data[0]

        # Knowing what happens in turbogears/visit/api.py when this is called,
        # we can shortcircuit this step and avoid a round trip to the FAS
        # server.
        # if visit_key != session_id:
        #     # visit has expired
        #     return None
        # # Hitting FAS has already updated the visit.
        # return Visit(visit_key, False)
        log.debug('JsonFasVisitManager.visit_for_key: exit')
        if visit_key != session_id:
            return Visit(session_id, True)
        else:
            return Visit(visit_key, False)

    def update_queued_visits(self, queue):
        '''Update the visit information on the server'''
        log.debug('JsonFasVisitManager.update_queued_visits: enter')
        # Hit any URL in fas with each visit_key to update the sessions
        for visit_key in queue:
            log.info(_('updating visit (%s)'), visit_key)
            self.fas.refresh_session(visit_key)
        log.debug('JsonFasVisitManager.update_queued_visits: exit')