source: python-fedora-devel/fedora/client/fas2.py @ python-fedora-devel,844

Revision python-fedora-devel,844, 32.5 KB checked in by Toshio Kuratomi <toshio@…>, 18 months ago (diff)

Fix doc error generating fas2 warning.

Line 
1# -*- coding: utf-8 -*-
2#
3# Copyright (C) 2008-2012  Ricky Zhou, Red Hat, Inc.
4# This file is part of python-fedora
5#
6# python-fedora is free software; you can redistribute it and/or
7# modify it under the terms of the GNU Lesser General Public
8# License as published by the Free Software Foundation; either
9# version 2.1 of the License, or (at your option) any later version.
10#
11# python-fedora is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14# Lesser General Public License for more details.
15#
16# You should have received a copy of the GNU Lesser General Public
17# License along with python-fedora; if not, see <http://www.gnu.org/licenses/>
18#
19'''
20Provide a client module for talking to the Fedora Account System.
21
22
23.. moduleauthor:: Ricky Zhou <ricky@fedoraproject.org>
24.. moduleauthor:: Toshio Kuratomi <tkuratom@redhat.com>
25.. moduleauthor:: Ralph Bean <rbean@redhat.com>
26'''
27import itertools
28import urllib
29import warnings
30
31from bunch import Bunch
32from kitchen.text.converters import to_bytes
33
34try:
35    from hashlib import md5
36except ImportError:
37    from md5 import new as md5
38
39from fedora.client import AppError, BaseClient, FasProxyClient, \
40        FedoraClientError, FedoraServiceError
41from fedora import __version__, b_
42
43### FIXME: To merge:
44# /usr/bin/fasClient from fas
45# API from Will Woods
46# API from MyFedora
47
48class FASError(FedoraClientError):
49    '''FAS Error'''
50    pass
51
52class CLAError(FASError):
53    '''CLA Error'''
54    pass
55
56USERFIELDS = ['affiliation', 'bugzilla_email', 'certificate_serial',
57        'comments', 'country_code', 'creation', 'email', 'emailtoken',
58        'facsimile', 'gpg_keyid', 'human_name', 'id', 'internal_comments',
59        'ircnick', 'latitude', 'last_seen', 'longitude', 'password',
60        'password_changed', 'passwordtoken', 'postal_address', 'privacy',
61        'locale', 'ssh_key', 'status', 'status_change', 'telephone',
62        'unverified_email', 'timezone', 'username', 'security_question', 
63        'security_answer', ]
64
65class AccountSystem(BaseClient):
66    '''An object for querying the Fedora Account System.
67
68    The Account System object provides a python API for talking to the Fedora
69    Account System.  It abstracts the http requests, cookie handling, and
70    other details so you can concentrate on the methods that are important to
71    your program.
72
73    .. warning::
74
75        If your code is trying to use the AccountSystem object to
76        connect to fas for multiple users you probably want to use
77        :class:`~fedora.client.FasProxyClient` instead.  If your code is
78        trying to reuse a single instance of AccountSystem for multiple users
79        you *definitely* want to use :class:`~fedora.client.FasProxyClient`
80        instead.  Using AccountSystem in these cases may result in a user
81        being logged in as a different user.  (This may be the case even if
82        you instantiate a new AccountSystem object for each user if
83        :attr:cache_session: is True since that creates a file on the file
84        system that can end up loading session credentials for the wrong
85        person.
86
87    .. versionchanged:: 0.3.26
88        Added :meth:`~fedora.client.AccountSystem.gravatar_url` that returns
89        a url to a gravatar for a user.
90    '''
91    # proxy is a thread-safe connection to the fas server for verifying
92    # passwords of other users
93    proxy = None
94
95    # size that we allow to request from gravatar.com
96    _valid_gravatar_sizes = (32, 64, 140)
97
98    def __init__(self, base_url='https://admin.fedoraproject.org/accounts/',
99            *args, **kwargs):
100        '''Create the AccountSystem client object.
101
102        :kwargs base_url: Base of every URL used to contact the server.
103            Defaults to the Fedora Project FAS instance.
104        :kwargs useragent: useragent string to use.  If not given, default to
105            "Fedora Account System Client/VERSION"
106        :kwargs debug: If True, log debug information
107        :kwargs username: username for establishing authenticated connections
108        :kwargs password: password to use with authenticated connections
109        :kwargs session_cookie: **Deprecated** Use session_id instead.
110            User's session_cookie to connect to the server
111        :kwargs session_id: user's session_id to connect to the server
112        :kwargs cache_session: if set to true, cache the user's session cookie
113            on the filesystem between runs.
114        '''
115        if 'useragent' not in kwargs:
116            kwargs['useragent'] = 'Fedora Account System Client/%s' \
117                    % __version__
118        super(AccountSystem, self).__init__(base_url, *args, **kwargs)
119        # We need a single proxy for the class to verify username/passwords
120        # against.
121        if not self.proxy:
122            self.proxy = FasProxyClient(base_url, useragent=self.useragent,
123                    session_as_cookie=False, debug=self.debug,
124                    insecure=self.insecure)
125
126        # Preseed a list of FAS accounts with bugzilla addresses
127        # This allows us to specify a different email for bugzilla than is
128        # in the FAS db.  It is a hack, however, until FAS has a field for the
129        # bugzilla address.
130        self.__bugzilla_email = {
131                # Konstantin Ryabitsev: mricon@gmail.com
132                100029: 'icon@fedoraproject.org',
133                # Sean Reifschneider: jafo@tummy.com
134                100488: 'jafo-redhat@tummy.com',
135                # Karen Pease: karen-pease@uiowa.edu
136                100281: 'meme@daughtersoftiresias.org',
137                # Robert Scheck: redhat@linuxnetz.de
138                100093: 'redhat-bugzilla@linuxnetz.de',
139                # Scott Bakers: bakers@web-ster.com
140                100881: 'scott@perturb.org',
141                # Colin Charles: byte@aeon.com.my
142                100014: 'byte@fedoraproject.org',
143                # W. Michael Petullo: mike@flyn.org
144                100136: 'redhat@flyn.org',
145                # Elliot Lee: sopwith+fedora@gmail.com
146                100060: 'sopwith@redhat.com',
147                # Control Center Team: Bugzilla user but email doesn't exist
148                9908: 'control-center-maint@redhat.com',
149                # Máirín Duffy
150                100548: 'duffy@redhat.com',
151                # Muray McAllister: murray.mcallister@gmail.com
152                102321: 'mmcallis@redhat.com',
153                # William Jon McCann: mccann@jhu.edu
154                102489: 'jmccann@redhat.com',
155                # Matt Domsch's rebuild script -- bz email goes to /dev/null
156                103590: 'ftbfs@fedoraproject.org',
157                # Sindre Pedersen BjÞrdal: foolish@guezz.net
158                100460 : 'sindrepb@fedoraproject.org',
159                # Jesus M. Rodriguez: jmrodri@gmail.com
160                102180: 'jesusr@redhat.com',
161                # Roozbeh Pournader: roozbeh@farsiweb.info
162                100350: 'roozbeh@gmail.com',
163                # Michael DeHaan: michael.dehaan@gmail.com
164                100603: 'mdehaan@redhat.com',
165                # Sebastian Gosenheimer: sgosenheimer@googlemail.com
166                103647: 'sebastian.gosenheimer@proio.com',
167                # Ben Konrath: bkonrath@redhat.com
168                101156: 'ben@bagu.org',
169                # Kai Engert: kaie@redhat.com
170                100399: 'kengert@redhat.com',
171                # William Jon McCann: william.jon.mccann@gmail.com
172                102952: 'jmccann@redhat.com',
173                # Simon Wesp: simon@w3sp.de
174                109464: 'cassmodiah@fedoraproject.org',
175                # Robert M. Albrecht: romal@gmx.de
176                101475: 'mail@romal.de',
177                # Mathieu Bridon: mathieu.bridon@gmail.com
178                100753: 'bochecha@fedoraproject.org',
179                # Davide Cescato: davide.cescato@iaeste.ch
180                123204: 'ceski@fedoraproject.org',
181                # Nick Bebout: nick@bebout.net
182                101458: 'nb@fedoraproject.org',
183                # Niels Haase: haase.niels@gmail.com
184                126862: 'arxs@fedoraproject.org',
185                # Thomas Janssen: th.p.janssen@googlemail.com
186                103110: 'thomasj@fedoraproject.org',
187                # Michael J Gruber: 'michaeljgruber+fedoraproject@gmail.com'
188                105113: 'mjg@fedoraproject.org',
189                # Juan Manuel Rodriguez Moreno: 'nushio@gmail.com'
190                101302: 'nushio@fedoraproject.org',
191                # Andrew Cagney: 'andrew.cagney@gmail.com'
192                102169: 'cagney@fedoraproject.org',
193                # Jeremy Katz: 'jeremy@katzbox.net'
194                100036: 'katzj@fedoraproject.org',
195                # Dominic Hopf: 'dmaphy@gmail.com'
196                124904: 'dmaphy@fedoraproject.org',
197                # Christoph Wickert: 'christoph.wickert@googlemail.com':
198                100271: 'cwickert@fedoraproject.org',
199                # Elliott Baron: 'elliottbaron@gmail.com'
200                106760: 'ebaron@fedoraproject.org',
201                # Thomas Spura: 'spurath@students.uni-mainz.de'
202                111433: 'tomspur@fedoraproject.org',
203                # Adam Miller: 'maxamillion@gmail.com'
204                110673: 'admiller@redhat.com',
205                # Garrett Holmstrom: 'garrett.holmstrom@gmail.com'
206                131739: 'gholms@fedoraproject.org',
207                # Tareq Al Jurf: taljurf.fedora@gmail.com
208                109863: 'taljurf@fedoraproject.org',
209                # Josh Kayse: jokajak@gmail.com
210                148243: 'jokajak@fedoraproject.org',
211                # Behdad Esfahbod: fedora@behdad.org
212                100102: 'behdad@fedoraproject.org',
213                # Daniel Bruno: danielbrunos@gmail.com
214                101608: 'dbruno@fedoraproject.org',
215                # Beth Lynn Eicher: bethlynneicher@gmail.com
216                148706: 'bethlynn@fedoraproject.org',
217                # Andre Robatino: andre.robatino@verizon.net
218                114970: 'robatino@fedoraproject.org',
219                # Jeff Sheltren: jeff@tag1consulting.com
220                100058: 'sheltren@fedoraproject.org',
221                # Josh Boyer: jwboyer@gmail.com
222                100115: 'jwboyer@redhat.com',
223                # Matthew Miller: mattdm@mattdm.org
224                100042: 'mattdm@redhat.com',
225                # Jamie Nguyen: j@jamielinux.com
226                160587: 'jamielinux@fedoraproject.org',
227                # Nikos Roussos: nikos@roussos.cc
228                144436: 'comzeradd@fedoraproject.org',
229                }
230        # A few people have an email account that is used in owners.list but
231        # have setup a bugzilla account for their primary account system email
232        # address now.  Map these here.
233        self.__alternate_email = {
234                # Damien Durand: splinux25@gmail.com
235                'splinux@fedoraproject.org': 100406,
236                # Kevin Fenzi: kevin@tummy.com
237                'kevin-redhat-bugzilla@tummy.com': 100037,
238                }
239        for bugzilla_map in self.__bugzilla_email.items():
240            self.__alternate_email[bugzilla_map[1]] = bugzilla_map[0]
241
242        # We use the two mappings as follows::
243        # When looking up a user by email, use __alternate_email.
244        # When looking up a bugzilla email address use __bugzilla_email.
245        #
246        # This allows us to parse in owners.list and have a value for all the
247        # emails in there while not using the alternate email unless it is
248        # the only option.
249
250    # TODO: Use exceptions properly
251
252    ### Set insecure properly ###
253    # When setting insecure, we have to set it both on ourselves and on
254    # self.proxy
255    def _get_insecure(self):
256        return self._insecure
257
258    def _set_insecure(self, insecure):
259        self._insecure = insecure
260        self.proxy = FasProxyClient(self.base_url, useragent=self.useragent,
261                session_as_cookie=False, debug=self.debug, insecure=insecure)
262        return insecure
263    #: If this attribute is set to True, do not check server certificates
264    #: against their CA's.  This means that man-in-the-middle attacks are
265    #: possible. You might turn this option on for testing against a local
266    #: version of a server with a self-signed certificate but it should be off
267    #: in production.
268    insecure = property(_get_insecure, _set_insecure)
269
270    ### Groups ###
271   
272    def create_group(self, name, display_name, owner, group_type,
273                     invite_only=0, needs_sponsor=0, user_can_remove=1,
274                     prerequisite='', joinmsg='', apply_rules='None'):
275        '''Creates a FAS group.
276
277        :arg name: The short group name (alphanumeric only).
278        :arg display_name: A longer version of the group's name.
279        :arg owner: The username of the FAS account which owns the new group.
280        :arg group_type: The kind of group being created. Current valid options
281            are git, svn, hg, shell, and tracking.
282        :kwarg invite_only: Users must be invited to the group, they cannot
283            join on their own.
284        :kwarg needs_sponsor: Users must be sponsored into the group.
285        :kwarg user_can_remove: Users can remove themselves from the group.
286        :kwarg prerequisite: Users must be in the given group (string) before
287            they can join the new group.
288        :kwarg joinmsg: A message shown to users when they apply to the group.
289        :kwarg apply_rules: Rules for applying to the group, shown to users
290            before they apply.
291        :rtype: :obj:`bunch.Bunch`
292        :returns: A Bunch containing information about the group that was
293            created.
294
295        .. versionadded:: 0.3.29
296        '''
297        req_params = {
298            'invite_only': invite_only,
299            'needs_sponsor': needs_sponsor,
300            'user_can_remove': user_can_remove,
301            'prerequisite': prerequisite,
302            'joinmsg': joinmsg,
303            'apply_rules': apply_rules
304        }
305
306        request = self.send_request('/group/create/%s/%s/%s/%s' % (
307                urllib.quote(name),
308                urllib.quote(display_name),
309                urllib.quote(owner),
310                urllib.quote(group_type)),
311                                    req_params=req_params,
312                                    auth=True)
313        return request
314       
315
316    def group_by_id(self, group_id):
317        '''Returns a group object based on its id'''
318        params = {'group_id': int(group_id)}
319        request = self.send_request('json/group_by_id', auth = True,
320                req_params = params)
321        if request['success']:
322            return request['group']
323        else:
324            return dict()
325
326    def group_by_name(self, groupname):
327        '''Returns a group object based on its name'''
328        params = {'groupname': groupname}
329        request = self.send_request('json/group_by_name', auth = True,
330                req_params = params)
331        if request['success']:
332            return request['group']
333        else:
334            raise AppError(message=b_('FAS server unable to retrieve group'
335                ' %(group)s') % {'group': to_bytes(groupname)},
336                name='FASError')
337
338    def group_members(self, groupname):
339        '''Return a list of people approved for a group.
340
341        This method returns a list of people who are in the requested group.
342        The people are all approved in the group.  Unapproved people are not
343        shown.  The format of data is::
344
345            \[{'username': 'person1', 'role_type': 'user'},
346            \{'username': 'person2', 'role_type': 'sponsor'}]
347
348        role_type can be one of 'user', 'sponsor', or 'administrator'.
349
350        .. versionadded:: 0.3.2
351        .. versionchanged:: 0.3.21
352            Return a Bunch instead of a DictContainer
353        '''
354        request = self.send_request('/group/dump/%s' %
355                urllib.quote(groupname), auth=True)
356
357        return [Bunch(username=user[0], role_type=user[3])
358                    for user in request['people']]
359
360    ### People ###
361
362    def person_by_id(self, person_id):
363        '''Returns a person object based on its id'''
364        person_id = int(person_id)
365        params = {'person_id': person_id}
366        request = self.send_request('json/person_by_id', auth=True,
367                req_params=params)
368
369        if request['success']:
370            if person_id in self.__bugzilla_email:
371                request['person']['bugzilla_email'] = \
372                        self.__bugzilla_email[person_id]
373            else:
374                request['person']['bugzilla_email'] = request['person']['email']
375            # In a devel version of FAS, membership info was returned separately
376            # This was later corrected (can remove this code at some point)
377            if 'approved' in request:
378                request['person']['approved_memberships'] = request['approved']
379            if 'unapproved' in request:
380                request['person']['unapproved_memberships'] = \
381                        request['unapproved']
382            return request['person']
383        else:
384            return dict()
385
386    def person_by_username(self, username):
387        '''Returns a person object based on its username'''
388        params = {'username': username}
389        request = self.send_request('json/person_by_username', auth = True,
390                req_params = params)
391
392        if request['success']:
393            person = request['person']
394            if person['id'] in self.__bugzilla_email:
395                person['bugzilla_email'] = self.__bugzilla_email[person['id']]
396            else:
397                person['bugzilla_email'] = person['email']
398            # In a devel version of FAS, membership info was returned separately
399            # This was later corrected (can remove this code at some point)
400            if 'approved' in request:
401                request['person']['approved_memberships'] = request['approved']
402            if 'unapproved' in request:
403                request['person']['unapproved_memberships'] = \
404                        request['unapproved']
405            return person
406        else:
407            return dict()
408
409    def gravatar_url(self, username, size=64,
410                     default=None, lookup_email=True):
411        ''' Returns a URL to a gravatar for a given username.
412
413        :arg username: FAS username to construct a gravatar url for
414        :kwarg size: size of the gravatar.  Allowed sizes are 32, 64, 140.
415            Default: 64
416        :kwarg default: If gravatar does not have a gravatar image for the
417            email address, this url is returned instead.  Default:
418            the fedora logo at the specified size.
419        :kwarg lookup_email:  If true, use the email from FAS, otherwise just
420            append @fedoraproject.org to the username.  Not that this will be
421            much slower if lookup_email is set to True since we'd have to make a
422            query against FAS itself.
423        :raises ValueError: if the size parameter is not allowed
424        :rtype: :obj:`str`
425        :returns: url of a gravatar for the user
426
427        If that user has no gravatar entry, instruct gravatar.com to redirect
428        us to the Fedora logo.
429
430        If that user has no email attribute, then make a fake request to
431        gravatar.
432
433        .. versionadded:: 0.3.26
434        .. versionchanged: 0.3.30
435            Add lookup_email parameter to control whether we generate gravatar
436            urls with the email in fas or username@fedoraproject.org
437        '''
438        if size not in self._valid_gravatar_sizes:
439            raise ValueError(b_('Size %(size)i disallowed.  Must be in'
440                ' %(valid_sizes)r') % { 'size': size,
441                    'valid_sizes': self._valid_gravatar_sizes})
442
443        if not default:
444            default = "http://fedoraproject.org/static/images/" + \
445                    "fedora_infinity_%ix%i.png" % (size, size)
446
447        query_string = urllib.urlencode({
448            's': size,
449            'd': default,
450        })
451
452        if lookup_email:
453            person = self.person_by_username(username)
454            email = person.get('email', 'no_email')
455        else:
456            email = "%s@fedoraproject.org" % username
457
458        hash = md5(email).hexdigest()
459
460        return "http://www.gravatar.com/avatar/%s?%s" % (hash, query_string)
461
462    def user_id(self):
463        '''Returns a dict relating user IDs to usernames'''
464        request = self.send_request('json/user_id', auth=True)
465        people = {}
466        for person_id, username in request['people'].items():
467            # change userids from string back to integer
468            people[int(person_id)] = username
469        return people
470
471    def people_by_key(self, key=u'username', search=u'*', fields=None):
472        '''Return a dict of people
473
474        :kwarg key: Key by this field.  Valid values are 'id', 'username', or
475            'email'.  Default is 'username'
476        :kwarg search: Pattern to match usernames against.  Defaults to the
477            '*' wildcard which matches everyone.
478        :kwarg fields: Limit the data returned to a specific list of fields.
479            The default is to retrieve all fields.
480            Valid fields are:
481
482                * affiliation
483                * alias_enabled
484                * bugzilla_email
485                * certificate_serial
486                * comments
487                * country_code
488                * creation
489                * email
490                * emailtoken
491                * facsimile
492                * gpg_keyid
493                * group_roles
494                * human_name
495                * id
496                * internal_comments
497                * ircnick
498                * last_seen
499                * latitude
500                * locale
501                * longitude
502                * memberships
503                * old_password
504                * password
505                * password_changed
506                * passwordtoken
507                * postal_address
508                * privacy
509                * roles
510                * ssh_key
511                * status
512                * status_change
513                * telephone
514                * timezone
515                * unverified_email
516                * username
517
518            Note that for most users who access this data, many of these
519            fields will be set to None due to security or privacy settings.
520        :returns: a dict relating the key value to the fields.
521
522        .. versionchanged:: 0.3.21
523            Return a Bunch instead of a DictContainer
524        .. versionchanged:: 0.3.26
525            Fixed to return a list with both people who have signed the CLA
526            and have not
527        '''
528        # Make sure we have a valid key value
529        if key not in ('id', 'username', 'email'):
530            raise KeyError(b_('key must be one of "id", "username", or'
531                ' "email"'))
532
533        if fields:
534            fields = list(fields)
535            for field in fields:
536                if field not in USERFIELDS:
537                    raise KeyError(b_('%(field)s is not a valid field to'
538                        ' filter') % {'field': to_bytes(field)})
539        else:
540            fields = USERFIELDS
541
542        # Make sure we retrieve the key value
543        unrequested_fields = []
544        if key not in fields:
545            unrequested_fields.append(key)
546            fields.append(key)
547        if 'bugzilla_email' in fields:
548            # Need id and email for the bugzilla information
549            if 'id' not in fields:
550                unrequested_fields.append('id')
551                fields.append('id')
552            if 'email' not in fields:
553                unrequested_fields.append('email')
554                fields.append('email')
555
556        request = self.send_request('/user/list', req_params={'search': search,
557            'fields': [f for f in fields if f != 'bugzilla_email']}, auth=True)
558
559        people = Bunch()
560        for person in itertools.chain(request['people'],
561                request['unapproved_people']):
562            # Retrieve bugzilla_email from our list if necessary
563            if 'bugzilla_email' in fields:
564                if person['id'] in self.__bugzilla_email:
565                    person['bugzilla_email'] = \
566                            self.__bugzilla_email[person['id']]
567                else:
568                    person['bugzilla_email'] = person['email']
569
570            person_key = person[key]
571            # Remove any fields that weren't requested by the user
572            if unrequested_fields:
573                for field in unrequested_fields:
574                    del person[field]
575
576            # Add the person record to the people dict
577            people[person_key] = person
578
579        return people
580
581    def people_by_id(self):
582        '''*Deprecated* Use people_by_key() instead.
583
584        Returns a dict relating user IDs to human_name, email, username,
585        and bugzilla email
586
587        .. versionchanged:: 0.3.21
588            Return a Bunch instead of a DictContainer
589        '''
590        warnings.warn(b_("people_by_id() is deprecated and will be removed in"
591            " 0.4.  Please port your code to use people_by_key(key='id',"
592            " fields=['human_name', 'email', 'username', 'bugzilla_email'])"
593            " instead"), DeprecationWarning, stacklevel=2)
594
595        request = self.send_request('/json/user_id', auth=True)
596        user_to_id = {}
597        people = Bunch()
598        for person_id, username in request['people'].items():
599            person_id = int(person_id)
600            # change userids from string back to integer
601            people[person_id] = {'username': username, 'id': person_id}
602            user_to_id[username] = person_id
603
604        # Retrieve further useful information about the users
605        request = self.send_request('/group/dump', auth=True)
606        for user in request['people']:
607            userid = user_to_id[user[0]]
608            person = people[userid]
609            person['email'] = user[1]
610            person['human_name'] = user[2]
611            if userid in self.__bugzilla_email:
612                person['bugzilla_email'] = self.__bugzilla_email[userid]
613            else:
614                person['bugzilla_email'] = person['email']
615
616        return people
617
618    ### Utils ###
619
620    def people_by_groupname(self, groupname):
621        '''Return a list of persons for the given groupname.
622
623        :arg groupname: Name of the group to look up
624        :returns: A list of person objects from the group.  If the group
625            contains no entries, then an empty list is returned.
626        '''
627        people = self.people_by_id()
628        group = dict(self.group_by_name(groupname))
629        userids = [user[u'person_id'] for user in
630                   group[u'approved_roles'] + group[u'unapproved_roles']]
631        return [people[userid] for userid in userids]
632
633    ### Configs ###
634
635    def get_config(self, username, application, attribute):
636        '''Return the config entry for the key values.
637
638        :arg username: Username of the person
639        :arg application: Application for which the config is set
640        :arg attribute: Attribute key to lookup
641        :raises AppError: if the server returns an exception
642        :returns: The unicode string that describes the value.  If no entry
643            matched the username, application, and attribute then None is
644            returned.
645        '''
646        request = self.send_request('config/list/%s/%s/%s' %
647                (username, application, attribute), auth=True)
648        if 'exc' in request:
649            raise AppError(name = request['exc'], message = request['tg_flash'])
650
651        # Return the value if it exists, else None.
652        if 'configs' in request and attribute in request['configs']:
653            return request['configs'][attribute]
654        return None
655
656    def get_configs_like(self, username, application, pattern=u'*'):
657        '''Return the config entries that match the keys and the pattern.
658
659        Note: authentication on the server will prevent anyone but the user
660        or a fas admin from viewing or changing their configs.
661
662        :arg username: Username of the person
663        :arg application: Application for which the config is set
664        :kwarg pattern: A pattern to select values for.  This accepts * as a
665            wildcard character. Default='*'
666        :raises AppError: if the server returns an exception
667        :returns: A dict mapping ``attribute`` to ``value``.
668        '''
669        request = self.send_request('config/list/%s/%s/%s' %
670                (username, application, pattern), auth=True)
671        if 'exc' in request:
672            raise AppError(name = request['exc'], message = request['tg_flash'])
673
674        return request['configs']
675
676    def set_config(self, username, application, attribute, value):
677        '''Set a config entry in FAS for the user.
678
679        Note: authentication on the server will prevent anyone but the user
680        or a fas admin from viewing or changing their configs.
681
682        :arg username: Username of the person
683        :arg application: Application for which the config is set
684        :arg attribute: The name of the config key that we're setting
685        :arg value: The value to set this to
686        :raises AppError: if the server returns an exception
687        '''
688        request = self.send_request('config/set/%s/%s/%s' %
689                (username, application, attribute),
690                req_params={'value': value}, auth=True)
691
692        if 'exc' in request:
693            raise AppError(name = request['exc'], message = request['tg_flash'])
694
695    def people_query(self, constraints=None, columns=None):
696        '''Returns a list of dicts representing database rows
697
698        :arg constraints: A dictionary specifying WHERE constraints on columns
699        :arg columns: A list of columns to be selected in the query
700        :raises AppError: if the query failed on the server (most likely
701            because  the server was given a bad query)
702        :returns: A list of dicts representing database rows (the keys of
703            the dict are the columns requested)
704
705        .. versionadded:: 0.3.12.1
706        '''
707        if constraints is None:
708            constraints = {}
709        if columns is None:
710            columns = []
711
712        req_params = {}
713        req_params.update(constraints)
714        req_params['columns'] = ','.join(columns)
715
716        try:
717            request = self.send_request('json/people_query',
718                req_params=req_params, auth=True)
719            if request['success']:
720                return request['data']
721            else:
722                raise AppError(message=request['error'], name='FASError')
723        except FedoraServiceError:
724            raise
725
726    ### Certs ###
727
728    def user_gencert(self):
729        '''Generate a cert for a user'''
730        try:
731            request = self.send_request('user/dogencert', auth=True)
732        except FedoraServiceError:
733            raise
734        if not request['cla']:
735            raise CLAError
736        return "%(cert)s\n%(key)s" % request
737
738    ### Passwords ###
739
740    def verify_password(self, username, password):
741        '''Return whether the username and password pair are valid.
742
743        :arg username: username to try authenticating
744        :arg password: password for the user
745        :returns: True if the username/password are valid.  False otherwise.
746        '''
747        return self.proxy.verify_password(username, password)
748
749    ### fasClient Special Methods ###
750
751    def group_data(self, force_refresh=None):
752        '''Return administrators/sponsors/users and group type for all groups
753
754        :arg force_refresh: If true, the returned data will be queried from the
755            database, as opposed to memcached.
756        :raises AppError: if the query failed on the server
757        :returns: A dict mapping group names to the group type and the
758            user IDs of the administrator, sponsors, and users of the group.
759
760        .. versionadded:: 0.3.8
761        '''
762        params = {}
763        if force_refresh:
764            params['force_refresh'] = True
765
766        try:
767            request = self.send_request('json/fas_client/group_data',
768                req_params=params, auth=True)
769            if request['success']:
770                return request['data']
771            else:
772                raise AppError(message=b_('FAS server unable to retrieve'
773                    ' group members'), name='FASError')
774        except FedoraServiceError:
775            raise
776
777    def user_data(self):
778        '''Return user data for all users in FAS
779
780        Note: If the user is not authorized to see password hashes,
781        '*' is returned for the hash.
782
783        :raises AppError: if the query failed on the server
784        :returns: A dict mapping user IDs to a username, password hash,
785            SSH public key, email address, and status.
786
787        .. versionadded:: 0.3.8
788        '''
789        try:
790            request = self.send_request('json/fas_client/user_data', auth=True)
791            if request['success']:
792                return request['data']
793            else:
794                raise AppError(message=b_('FAS server unable to retrieve user'
795                    ' information'), name='FASError')
796        except FedoraServiceError:
797            raise
798
Note: See TracBrowser for help on using the repository browser.