diff --git a/fas/fas/auth.py b/fas/fas/auth.py index 7750b51..30d3e5f 100644 --- a/fas/fas/auth.py +++ b/fas/fas/auth.py @@ -89,7 +89,7 @@ def canViewGroup(userName, groupName, g=None): if re.compile(privilegedViewGroups).match(groupName): if not g: g = Groups.byUserName(userName) - if canAdminGroup(userName, groupName): + if canAdminGroup(userName, groupName, g): return True else: return False @@ -97,9 +97,18 @@ def canViewGroup(userName, groupName, g=None): return True def canApplyGroup(userName, groupName, applyUserName, g=None): - # This is where we could make groups depend on other ones. if not g: g = Groups.byUserName(userName) + # User must satisfy all dependencies to join. + # This is bypassed for people already in the group and for the + # owner of the group (when they initially make it). + group = Groups.groups(groupName)[groupName] + requirements = group.fedoraGroupRequires.split() + for req in requirements: + try: + g[req].cn + except KeyError: + return False # A user can apply themselves, and FAS admins can apply other people. if (userName == applyUserName) or \ isAdmin(userName, g): diff --git a/fas/fas/fasLDAP.py b/fas/fas/fasLDAP.py index 4eb2fa7..e1e5d9f 100644 --- a/fas/fas/fasLDAP.py +++ b/fas/fas/fasLDAP.py @@ -60,13 +60,15 @@ class Server(object): ''' Modify an attribute, requires write access ''' if new in (old, 'None'): return None - if old == None: - old = 'None' - o = { attribute : old } - n = { attribute : new } - ldif = ldap.modlist.modifyModlist(o, n) + #o = { attribute : old } + #n = { attribute : new } + ldif = [] + ldif.append((ldap.MOD_DELETE,attribute,None)) + ldif.append((ldap.MOD_ADD,attribute,new)) + + #ldif = ldap.modlist.modifyModlist(o, n, ignore_oldexistent=1) # commit self.ldapConn.modify_s(base, ldif) @@ -103,13 +105,14 @@ class Group(object): __server = Server() __base = 'ou=FedoraGroups,dc=fedoraproject,dc=org' - def __init__(self, cn, fedoraGroupDesc, fedoraGroupOwner, fedoraGroupType, fedoraGroupNeedsSponsor, fedoraGroupUserCanRemove, fedoraGroupJoinMsg): + def __init__(self, cn, fedoraGroupDesc, fedoraGroupOwner, fedoraGroupType, fedoraGroupNeedsSponsor, fedoraGroupUserCanRemove, fedoraGroupRequires, fedoraGroupJoinMsg): self.cn = cn self.fedoraGroupDesc = fedoraGroupDesc self.fedoraGroupOwner = fedoraGroupOwner self.fedoraGroupType = fedoraGroupType self.fedoraGroupNeedsSponsor = fedoraGroupNeedsSponsor self.fedoraGroupUserCanRemove = fedoraGroupUserCanRemove + self.fedoraGroupRequires = fedoraGroupRequires self.fedoraGroupJoinMsg = fedoraGroupJoinMsg def __json__(self): @@ -119,11 +122,12 @@ class Group(object): 'fedoraGroupType': self.fedoraGroupType, 'fedoraGroupNeedsSponsor': self.fedoraGroupNeedsSponsor, 'fedoraGroupUserCanRemove': self.fedoraGroupUserCanRemove, + 'fedoraGroupRequires': self.fedoraGroupRequires, 'fedoraGroupJoinMsg': self.fedoraGroupJoinMsg } @classmethod - def newGroup(self, cn, fedoraGroupDesc, fedoraGroupOwner, fedoraGroupNeedsSponsor, fedoraGroupUserCanRemove, fedoraGroupJoinMsg): + def newGroup(self, cn, fedoraGroupDesc, fedoraGroupOwner, fedoraGroupNeedsSponsor, fedoraGroupUserCanRemove, fedoraGroupRequires, fedoraGroupJoinMsg): ''' Create a new group ''' attributes = { 'cn' : cn, 'objectClass' : ('fedoraGroup'), @@ -132,6 +136,7 @@ class Group(object): 'fedoraGroupType' : '1', 'fedoraGroupNeedsSponsor' : fedoraGroupNeedsSponsor, 'fedoraGroupUserCanRemove' : fedoraGroupUserCanRemove, + 'fedoraGroupRequires' : fedoraGroupRequires, 'fedoraGroupJoinMsg' : fedoraGroupJoinMsg, } @@ -225,6 +230,7 @@ class Groups(object): fedoraGroupType = group[0][1]['fedoraGroupType'][0], fedoraGroupNeedsSponsor = group[0][1]['fedoraGroupNeedsSponsor'][0], fedoraGroupUserCanRemove = group[0][1]['fedoraGroupUserCanRemove'][0], + fedoraGroupRequires = group[0][1]['fedoraGroupRequires'][0], fedoraGroupJoinMsg = group[0][1]['fedoraGroupJoinMsg'][0]) else: return None @@ -426,9 +432,13 @@ class Person(object): if not ldapServer: s = Server() ldapServer = s.ldapConn - who = 'cn=%s,ou=People,dc=fedoraproject,dc=org' % who - ldapServer.simple_bind_s(who, password) + try: + ldapServer.simple_bind_s(who, password) + except NO_SUCH_OBJECT: + raise AuthError + except INVALID_CREDENTIALS: + raise AuthError def upgrade(self, group): ''' Upgrade user in group ''' diff --git a/fas/fas/group.py b/fas/fas/group.py index 7a169af..d9dd4a1 100644 --- a/fas/fas/group.py +++ b/fas/fas/group.py @@ -24,6 +24,16 @@ class knownGroup(validators.FancyValidator): if not g: raise validators.Invalid(_("The group '%s' does not exist.") % value, value, state) +class groupsExist(validators.FancyValidator): + '''Make sure that required groups already exist''' + def _to_python(self, value, state): + return value.strip() + def validate_python(self, value, state): + for group in value.split(): + g = Groups.groups(group) + if not g: + raise validators.Invalid(_("The required group '%s' does not exist.") % value, value, state) + class unknownGroup(validators.FancyValidator): '''Make sure that a group doesn't already exist''' def _to_python(self, value, state): @@ -37,11 +47,13 @@ class createGroup(validators.Schema): groupName = validators.All(unknownGroup(not_empty=True, max=10), validators.String(max=32, min=3)) fedoraGroupDesc = validators.NotEmpty fedoraGroupOwner = validators.All(knownUser(not_empty=True, max=10), validators.String(max=32, min=3)) + fedoraGroupRequires = groupsExist class editGroup(validators.Schema): groupName = validators.All(knownGroup(not_empty=True, max=10), validators.String(max=32, min=3)) fedoraGroupDesc = validators.NotEmpty fedoraGroupOwner = validators.All(knownUser(not_empty=True, max=10), validators.String(max=32, min=3)) + fedoraGroupRequires = groupsExist class userNameGroupNameExists(validators.Schema): groupName = validators.All(knownGroup(not_empty=True, max=10), validators.String(max=32, min=3)) @@ -77,10 +89,10 @@ class Group(controllers.Controller): turbogears.redirect('/') return dict(tg_errors=tg_errors) + @identity.require(turbogears.identity.not_anonymous()) @validate(validators=groupNameExists()) @error_handler(error) @expose(template="fas.templates.group.view") - @identity.require(turbogears.identity.not_anonymous()) def view(self, groupName): '''View group''' userName = turbogears.identity.current.user_name @@ -108,8 +120,8 @@ class Group(controllers.Controller): value = {'groupName': groupName} return dict(userName=userName, groups=groups, group=group, me=me, value=value) - @expose(template="fas.templates.group.new") @identity.require(turbogears.identity.not_anonymous()) + @expose(template="fas.templates.group.new") def new(self): '''Display create group form''' userName = turbogears.identity.current.user_name @@ -118,11 +130,11 @@ class Group(controllers.Controller): turbogears.redirect('/') return dict() + @identity.require(turbogears.identity.not_anonymous()) @validate(validators=createGroup()) @error_handler(error) @expose(template="fas.templates.group.new") - @identity.require(turbogears.identity.not_anonymous()) - def create(self, groupName, fedoraGroupDesc, fedoraGroupOwner, fedoraGroupNeedsSponsor="FALSE", fedoraGroupUserCanRemove="FALSE", fedoraGroupJoinMsg=""): + def create(self, groupName, fedoraGroupDesc, fedoraGroupOwner, fedoraGroupNeedsSponsor="FALSE", fedoraGroupUserCanRemove="FALSE", fedoraGroupRequires="", fedoraGroupJoinMsg=""): '''Create a group''' userName = turbogears.identity.current.user_name if not canCreateGroup(userName): @@ -134,6 +146,7 @@ class Group(controllers.Controller): fedoraGroupOwner.encode('utf8'), fedoraGroupNeedsSponsor.encode('utf8'), fedoraGroupUserCanRemove.encode('utf8'), + fedoraGroupRequires.encode('utf8'), fedoraGroupJoinMsg.encode('utf8'),) except: @@ -153,8 +166,10 @@ class Group(controllers.Controller): turbogears.redirect('/group/view/%s' % groupName) return dict() - @expose(template="fas.templates.group.edit") @identity.require(turbogears.identity.not_anonymous()) + @validate(validators=groupNameExists()) + @error_handler(error) + @expose(template="fas.templates.group.edit") def edit(self, groupName): '''Display edit group form''' userName = turbogears.identity.current.user_name @@ -168,17 +183,19 @@ class Group(controllers.Controller): 'fedoraGroupType': group.fedoraGroupType, 'fedoraGroupNeedsSponsor': (group.fedoraGroupNeedsSponsor.upper() == 'TRUE'), 'fedoraGroupUserCanRemove': (group.fedoraGroupUserCanRemove.upper() == 'TRUE'), - #'fedoraGroupRequires': group.fedoraGroupRequires, + 'fedoraGroupRequires': group.fedoraGroupRequires, 'fedoraGroupJoinMsg': group.fedoraGroupJoinMsg, } return dict(value=value) + @identity.require(turbogears.identity.not_anonymous()) @validate(validators=editGroup()) @error_handler(error) @expose() - @identity.require(turbogears.identity.not_anonymous()) - def save(self, groupName, fedoraGroupDesc, fedoraGroupOwner, fedoraGroupType=1, fedoraGroupNeedsSponsor="FALSE", fedoraGroupUserCanRemove="FALSE", fedoraGroupJoinMsg=""): + def save(self, groupName, fedoraGroupDesc, fedoraGroupOwner, fedoraGroupType=1, fedoraGroupNeedsSponsor="FALSE", fedoraGroupUserCanRemove="FALSE", fedoraGroupRequires="", fedoraGroupJoinMsg=""): '''Edit a group''' userName = turbogears.identity.current.user_name + if fedoraGroupRequires == None: + fedoraGroupRequires = "" if not canEditGroup(userName, groupName): turbogears.flash(_("You cannot edit '%s'.") % groupName) turbogears.redirect('/group/view/%s' % groupName) @@ -191,6 +208,7 @@ class Group(controllers.Controller): server.modify(base, 'fedoraGroupType', str(fedoraGroupType).encode('utf8')) server.modify(base, 'fedoraGroupNeedsSponsor', fedoraGroupNeedsSponsor.encode('utf8')) server.modify(base, 'fedoraGroupUserCanRemove', fedoraGroupUserCanRemove.encode('utf8')) + server.modify(base, 'fedoraGroupRequires', fedoraGroupRequires.encode('utf8')) server.modify(base, 'fedoraGroupJoinMsg', fedoraGroupJoinMsg.encode('utf8')) try: 1 @@ -201,8 +219,8 @@ class Group(controllers.Controller): turbogears.redirect('/group/view/%s' % groupName) return dict() - @expose(template="fas.templates.group.list", allow_json=True) @identity.require(turbogears.identity.not_anonymous()) + @expose(template="fas.templates.group.list", allow_json=True) def list(self, search='*'): groups = Groups.groups(search) userName = turbogears.identity.current.user_name @@ -216,17 +234,17 @@ class Group(controllers.Controller): return ({'groups': groups}) return dict(groups=groups, search=search, myGroups=myGroups) + @identity.require(turbogears.identity.not_anonymous()) @validate(validators=userNameGroupNameExists()) @error_handler(error) @expose(template='fas.templates.group.view') - @identity.require(turbogears.identity.not_anonymous()) def apply(self, groupName, userName=None): '''Apply to a group''' applicant = turbogears.identity.current.user_name if not userName: userName = applicant if not canApplyGroup(applicant, groupName, userName): - turbogears.flash(_('You cannot apply %(user)s for %(group)s!') % \ + turbogears.flash(_('%(user)s could not apply to %(group)s!') % \ {'user': userName, 'group': groupName}) turbogears.redirect('/group/view/%s' % groupName) return dict() @@ -242,10 +260,10 @@ class Group(controllers.Controller): turbogears.redirect('/group/view/%s' % groupName) return dict() + @identity.require(turbogears.identity.not_anonymous()) @validate(validators=userNameGroupNameExists()) @error_handler(error) @expose(template='fas.templates.group.view') - @identity.require(turbogears.identity.not_anonymous()) def sponsor(self, groupName, userName): '''Sponsor user''' sponsor = turbogears.identity.current.user_name @@ -265,10 +283,10 @@ class Group(controllers.Controller): turbogears.redirect('/group/view/%s' % groupName) return dict() + @identity.require(turbogears.identity.not_anonymous()) @validate(validators=userNameGroupNameExists()) @error_handler(error) @expose(template='fas.templates.group.view') - @identity.require(turbogears.identity.not_anonymous()) def remove(self, groupName, userName): '''Remove user from group''' # TODO: Add confirmation? @@ -290,10 +308,10 @@ class Group(controllers.Controller): turbogears.redirect('/group/view/%s' % groupName) return dict() + @identity.require(turbogears.identity.not_anonymous()) @validate(validators=userNameGroupNameExists()) @error_handler(error) @expose(template='fas.templates.group.view') - @identity.require(turbogears.identity.not_anonymous()) def upgrade(self, groupName, userName): '''Upgrade user in group''' sponsor = turbogears.identity.current.user_name @@ -316,10 +334,10 @@ class Group(controllers.Controller): turbogears.redirect('/group/view/%s' % groupName) return dict() + @identity.require(turbogears.identity.not_anonymous()) @validate(validators=userNameGroupNameExists()) @error_handler(error) @expose(template='fas.templates.group.view') - @identity.require(turbogears.identity.not_anonymous()) def downgrade(self, groupName, userName): '''Upgrade user in group''' sponsor = turbogears.identity.current.user_name @@ -342,10 +360,10 @@ class Group(controllers.Controller): turbogears.redirect('/group/view/%s' % groupName) return dict() + @identity.require(turbogears.identity.not_anonymous()) @validate(validators=groupNameExists()) @error_handler(error) @expose(template="genshi-text:fas.templates.group.dump", format="text", content_type='text/plain; charset=utf-8') - @identity.require(turbogears.identity.not_anonymous()) def dump(self, groupName): userName = turbogears.identity.current.user_name if not canViewGroup(userName, groupName): diff --git a/fas/fas/templates/group/edit.html b/fas/fas/templates/group/edit.html index 8abcc9a..9f62279 100644 --- a/fas/fas/templates/group/edit.html +++ b/fas/fas/templates/group/edit.html @@ -27,6 +27,10 @@ +
+ + +
diff --git a/fas/fas/templates/group/new.html b/fas/fas/templates/group/new.html index 3eed2bb..d0d8fe4 100644 --- a/fas/fas/templates/group/new.html +++ b/fas/fas/templates/group/new.html @@ -29,6 +29,10 @@
+
+ + +
diff --git a/fas/fas/user.py b/fas/fas/user.py index 9640c8e..e9d2923 100644 --- a/fas/fas/user.py +++ b/fas/fas/user.py @@ -84,6 +84,10 @@ class changePass(validators.Schema): passwordCheck = validators.String() chained_validators = [validators.FieldsMatch('password', 'passwordCheck')] +class userNameExists(validators.Schema): + userName = validators.All(knownUser(not_empty=True, max=10), validators.String(max=32, min=3)) + + class userNameExists(validators.Schema): userName = validators.All(knownUser(not_empty=True, max=10), validators.String(max=32, min=3)) @@ -137,8 +141,10 @@ class User(controllers.Controller): claDone=None return dict(user=user, groups=groups, groupsPending=groupsPending, groupdata=groupdata, claDone=claDone, personal=personal, admin=admin) - @expose(template="fas.templates.user.edit") @identity.require(turbogears.identity.not_anonymous()) + @validate(validators=userNameExists()) + @error_handler(error) + @expose(template="fas.templates.user.edit") def edit(self, userName=None): '''Edit a user ''' @@ -159,6 +165,7 @@ class User(controllers.Controller): 'description': user.description, } return dict(value=value) + @identity.require(turbogears.identity.not_anonymous()) @validate(validators=editUser()) @error_handler(error) @expose(template='fas.templates.user.edit') @@ -192,8 +199,8 @@ class User(controllers.Controller): 'description': description, } return dict(value=value) + @identity.require(turbogears.identity.in_group("accounts")) #TODO: Use auth.py @expose(template="fas.templates.user.list") - @identity.require(turbogears.identity.in_group("accounts")) def list(self, search="a*"): '''List users ''' @@ -248,15 +255,15 @@ class User(controllers.Controller): turbogears.redirect('/user/new') return dict() - @expose(template="fas.templates.user.changepass") @identity.require(turbogears.identity.not_anonymous()) + @expose(template="fas.templates.user.changepass") def changepass(self): return dict() + @identity.require(turbogears.identity.not_anonymous()) @validate(validators=changePass()) @error_handler(error) @expose(template="fas.templates.user.changepass") - @identity.require(turbogears.identity.not_anonymous()) def setpass(self, currentPassword, password, passwordCheck): userName = turbogears.identity.current.user_name try: diff --git a/fas/ldap/53fc-fedora-group.ldif b/fas/ldap/53fc-fedora-group.ldif index 4e32a55..326d02b 100644 --- a/fas/ldap/53fc-fedora-group.ldif +++ b/fas/ldap/53fc-fedora-group.ldif @@ -14,4 +14,4 @@ attributeTypes: ( 2.5.444.13 NAME 'fedoraGroupUserCanRemove' DESC 'boolean indic attributeTypes: ( 2.5.444.14 NAME 'fedoraGroupJoinMsg' DESC 'message received upon joining the group' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{1000} ) attributeTypes: ( 2.5.444.21 NAME 'fedoraGroupDesc' DESC 'group description' SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{100} ) attributeTypes: ( 2.5.444.20 NAME 'fedoraGroupRequires' DESC 'Requisites of this Group' EQUALITY caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15{50} ) -objectClasses: ( 2.5.555.3 NAME 'fedoraGroup' DESC 'A object describing a group entry' STRUCTURAL MUST ( cn $ fedoraGroupDesc $ fedoraGroupOwner ) MAY ( fedoraGroupJoinMsg $ fedoraGroupUsercanRemove $ fedoraGroupType $ fedoraGroupNeedsSponsor $ fedoraGroupUserCanRemove $ fedoraGroupRequires ) ) +objectClasses: ( 2.5.555.3 NAME 'fedoraGroup' DESC 'A object describing a group entry' STRUCTURAL MUST ( cn $ fedoraGroupDesc $ fedoraGroupOwner ) MAY ( fedoraGroupJoinMsg $ fedoraGroupUsercanRemove $ fedoraGroupType $ fedoraGroupNeedsSponsor $ fedoraGroupRequires ) ) diff --git a/fas/ldap/PgToLDAP.py b/fas/ldap/PgToLDAP.py index 58f4cb5..afe28b7 100644 --- a/fas/ldap/PgToLDAP.py +++ b/fas/ldap/PgToLDAP.py @@ -342,6 +342,7 @@ def main(): userLdif.append(["fedoraGroupUserCanRemove",[str(group[4])]]) #need to convert to bool userLdif.append(["fedoraGroupDesc",[str('Please fill out a Group Description')]]) #need to convert to bool #userLdif.append(["groupPrerequisite",[str(group[5])]]) + userLdif.append(["fedoraGroupRequires",[str(group[5])]]) # <- Hope this is added properly - Ricky #userLdif.append(["groupPrerequisite",prereq]) not currently in the schema userLdif.append(["fedoraGroupJoinMsg",[str(group[6]) or "None" ]]) ldifWriter.unparse("cn=" + str(group[7]) +",ou=FedoraGroups,dc=fedoraproject,dc=org" , userLdif )