Martin Kosek wrote:
On Fri, 2012-03-02 at 11:40 -0500, Rob Crittenden wrote:
Jan Cholasta wrote:
On 1.3.2012 20:57, Rob Crittenden wrote:
Rob Crittenden wrote:
Jan Cholasta wrote:
On 17.1.2012 04:55, Rob Crittenden wrote:
Jan Cholasta wrote:
Dne 13.1.2012 17:39, Rob Crittenden napsal(a):
Jan Cholasta wrote:
Dne 14.12.2011 16:21, Rob Crittenden napsal(a):
Jan Cholasta wrote:
Dne 14.12.2011 15:23, Rob Crittenden napsal(a):
Jan Cholasta wrote:
Dne 14.12.2011 05:20, Rob Crittenden napsal(a):
The sudo schema now defines sudoOrder, sudoNotBefore and
sudoNotAfter
but these weren't available in the sudorule plugin.

I've added support for these. sudoOrder enforces uniqueness
because
duplicates are undefined.

I also added support for a GeneralizedTime parameter type.
This is
similar to the existing AccessTime parameter but it only
handles a
single time value.

You should parse the date/time part of the value with
time.strptime(timestr, '%Y%m%d%H%M%S') instead of doing it
manually,
that way you'll get most of the validation for free.

Yes but it gives a crappy error message, just saying that some
data is
left over not what is wrong.

IMHO having a separate error message for every field in the time
string
(like you do in the patch) is an overkill, simple "invalid time"
and/or
"unknown time format" should suffice (we don't have errors like
"invalid
3rd octet" for IP adresses either).

Well, the work is done, hard to go back on a better error message.


Also, it would be nice to be able to enter the value in more
user-friendly format (e.g. "2011-12-14 13:01:25 +0100") and
normalize
that to LDAP generalized time.

When dealing with time there are so many ways to input and
display
the
same values this becomes difficult.

I'd expect that the times for these two attributes will be
relatively
simple and I somehow doubt users are going to want seconds, leap
seconds
or fractions, but we'll need to consider how to do it for future
consistency (otherwise we could have a case where time is
entered in
one
format for some attributes and another for others).

If we input in a nice way we need to output in the same way.

We could make the preferred input/output time format
user-configurable,
defaulting to current locale time format. This format would be
used
for
output. For input, we could go over a list of formats (first the
user-configured format, then current locale format, then a
handful of
"standard" formats like YYYY-MM-DD HH:MM:SS) and use the first
format
that can be successfully used to parse the time string.

See how far you get into the rabbit hole with even this simple
format?

I don't mind, as long as it is the right thing to do (IMHO) :)

Anyway, I think this could be done on the client side, so we might
use
your patch without changes. However, I would prefer if the
parameter
class was more generic, so we could use it (hypothetically) to
store
time in some other way than LDAP generalized time attribute (at
least
name it DateTime please).


Ok, I'm fine with that.

Thanks.



The LDAP GeneralizedTime needs to be either in GMT or include a
differential. This gets us into the territory where the client
could be
in a different timezone than the server which leads us to why we
dropped
AccessTime in the first place.

Speaking of time zones, the differential alone is not a sufficient
time
zone description, as it doesn't account for DST. Is there a way to
store
time in LDAP with full time zone name (just in case it's needed
sometime
in future)?

There is no way to store DST in LDAP (probably for good reason).
Oddly
enough the older LDAP v3 RFC (2252) strongly recommends using only
GMT
but the RFC that obsoletes it (4517) does not include this.

Thanks for the info.



So I'd like the user to supply the
timezone themselves so I don't have to guess (wrongly) and let
them
worry about differing timezones.

We don't have to guess, IIRC there is a way to get the local
timezone
differential in both Python and JavaScript, so the client could
supply
it automatically if necessary.

I was thinking more about non-IPA clients (like sudo and notBefore).

I think this can still be done at least in CLI, but it could be
done in
a separate patch.


Updated patches attached.

rob

Patch 919 doesn't cleanly apply on current master (neither does 916
BTW).

Honza


Rebased patch (and 916 too, separately).

rob

Patch 918:

1. LDAP generalized time allows you to omit minutes from time zone
differential, your code treats such values as invalid

2. IMO a better pattern could be used, such as
u'^([0-9]{2}){5,7}([.,][0-9]+)?([-+]([0-9]{2}){1,2}|Z)$'

3. 20120229000Z has malformed minutes, but the error message says
"Malformed seconds"

4. 20120229000+0000 has malformed minutes, but the error message says
"Missing operator for differential or malformed time string"

5. 201202290000+0000 is valid generalized time, but it causes "Missing
operator for differential or malformed time string" error

6. Invalid month/day combinations (such as 201202310000Z) are
treated as
valid

7. When + or - is missing, the error message says "Missing operator
..."
- IMO a better term than "operator" is "sign" in this case

8. The DateTime docstring includes grammar definition for MINUS, but
not
for PLUS, DOT or COMMA.

9. I'm not too fond of the _rule_required hack. Can't the same thing be
done in _validate_scalar?

10. pattern_errmsg should say "Must be of the form YYYYMMDDHH[MM]Z or
YYYYMMDDHH[MM]{+|-}HHMM"

There might be more bugs in validation that I didn't discover. I would
suggest you to use a more pythonic approach for the validation code
(e.g. use partition() to split the time zone and fraction from the rest
of the value, etc.), the current code is rather C-ish, hard to read and
apparently error-prone.


Patch 919:

1. sudoorder uniqueness is not properly checked in ipa sudorule-mod:

$ ipa sudorule-add rule1
-----------------------
Added Sudo Rule "rule1"
-----------------------
Rule name: rule1
Enabled: TRUE

$ ipa sudorule-add rule2 --order=0
-----------------------
Added Sudo Rule "rule2"
-----------------------
Rule name: rule2
Enabled: TRUE
Sudo order: 0

$ ipa sudorule-add rule3 --order=0
ipa: ERROR: invalid 'order': order must be a unique value (0 already
used by rule2)

$ ipa sudorule-mod rule1 --order=0
--------------------------
Modified Sudo Rule "rule1"
--------------------------
Rule name: rule1
Enabled: TRUE
Sudo order: 0

(now both rule1 and rule2 have sudoorder=0)

2. Shouldn't we check that sudonotbefore<= sudonotafter?

3. sudonotbefore param doc should say "Start of time interval for which
the entry is valid (YYYYMMDDHH[MM]Z)"

4. sudonotafter param doc should say "End of time interval for which
the
entry is valid (YYYYMMDDHH[MM]Z)"

5. In 60ipasudo.ldif, the ipaSudoRule object class is missing the
sudoOrder, sudoNotBefore and sudoNotAfter attributes. Is this OK?


Both patches need to be rebased.

Honza


Ok, lets take this one step at a time.

This updated patch adds schema for all three attributes but just a Param
for sudoOrder. I'll have to revisit DateTime for notbefore and notafter.

I addressed the issue with order and added a test for it.

Schema is fixed on new installs.

ipa-server-install fails with:

[02/Mar/2012:09:13:08 +0100] dse_read_one_file - The entry cn=schema in
file /etc/dirsrv/slapd-IDM-LAB-BOS-REDHAT-COM/schema/60ipasudo.ldif
(lineno: 1) is invalid, error code 21 (Invalid syntax) - object class
ipaSudoRule: Unknown allowed attribute type "sudoNotBefore"
[02/Mar/2012:09:13:08 +0100] dse - Please edit the file to correct the
reported problems and then restart the server.


rob

And now with updated API.txt. Forgot to squash the commit.

rob


Honza


And now the right patch

The issue with clean install is that the sudo attribute types are
shipped in 60sudo.ldif schema, but our schema 60ipasudo.ldif which uses
some attributeTypes defined in 60sudo.ldif is alphabetically before
60sudo.ldif and is this processed first. That's why upgrade worked and
clean install did not.

ACK if you squash with the attached patch which renames 60ipasudo.ldif
to 65ipasudo.ldif so that our schema is processed _after_ the bundled
60ipasudo.ldif.

Martin

Good idea, rolled into my patch.

I hadn't fixed Honza's mod problem completely either. 0 was essentially a special case because of the way I was looking to see if a sudoorder change was being requested. I fixed that too and added a specific test case for it.

rob
>From 828f7c8686082408fbe72282abac71fde705786e Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcrit...@redhat.com>
Date: Thu, 1 Mar 2012 14:02:28 -0500
Subject: [PATCH] Add support for sudoOrder

Update ipaSudoRule objectClass on upgrades to add new attributes.
Ensure uniqueness of sudoOrder in rules.

The attributes sudoNotBefore and sudoNotAfter are being added to
schema but not as Params.

https://fedorahosted.org/freeipa/ticket/1314
---
 API.txt                                   |    9 ++++--
 install/share/60ipasudo.ldif              |   39 -------------------------
 install/share/65ipasudo.ldif              |   39 +++++++++++++++++++++++++
 install/share/Makefile.am                 |    2 +-
 install/updates/10-sudo.update            |    2 +
 ipalib/plugins/sudorule.py                |   40 +++++++++++++++++++++++++
 ipaserver/install/dsinstance.py           |    2 +-
 tests/test_xmlrpc/test_sudorule_plugin.py |   45 +++++++++++++++++++++++++++--
 8 files changed, 131 insertions(+), 47 deletions(-)
 delete mode 100644 install/share/60ipasudo.ldif
 create mode 100644 install/share/65ipasudo.ldif

diff --git a/API.txt b/API.txt
index 88684dfadecafd6a24da9e7a05490006d4b8b235..9ba3ce4accb3cb16f3f77b51e8fc3cde1a7ffc15 100644
--- a/API.txt
+++ b/API.txt
@@ -2831,7 +2831,7 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), None)
 output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
 output: Output('value', <type 'unicode'>, None)
 command: sudorule_add
-args: 1,14,3
+args: 1,15,3
 arg: Str('cn', attribute=True, cli_name='sudorule_name', multivalue=False, primary_key=True, required=True)
 option: Str('description', attribute=True, cli_name='desc', multivalue=False, required=False)
 option: StrEnum('usercategory', attribute=True, cli_name='usercat', multivalue=False, required=False, values=(u'all',))
@@ -2839,6 +2839,7 @@ option: StrEnum('hostcategory', attribute=True, cli_name='hostcat', multivalue=F
 option: StrEnum('cmdcategory', attribute=True, cli_name='cmdcat', multivalue=False, required=False, values=(u'all',))
 option: StrEnum('ipasudorunasusercategory', attribute=True, cli_name='runasusercat', multivalue=False, required=False, values=(u'all',))
 option: StrEnum('ipasudorunasgroupcategory', attribute=True, cli_name='runasgroupcat', multivalue=False, required=False, values=(u'all',))
+option: Int('sudoorder', attribute=True, cli_name='order', default=0, multivalue=False, required=False)
 option: Str('externaluser', attribute=True, cli_name='externaluser', multivalue=False, required=False)
 option: Str('ipasudorunasextuser', attribute=True, cli_name='runasexternaluser', multivalue=False, required=False)
 option: Str('ipasudorunasextgroup', attribute=True, cli_name='runasexternalgroup', multivalue=False, required=False)
@@ -2936,7 +2937,7 @@ args: 1,0,1
 arg: Str('cn', attribute=True, cli_name='sudorule_name', multivalue=False, primary_key=True, query=True, required=True)
 output: Output('result', None, None)
 command: sudorule_find
-args: 1,16,4
+args: 1,17,4
 arg: Str('criteria?', noextrawhitespace=False)
 option: Str('cn', attribute=True, autofill=False, cli_name='sudorule_name', multivalue=False, primary_key=True, query=True, required=False)
 option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, query=True, required=False)
@@ -2945,6 +2946,7 @@ option: StrEnum('hostcategory', attribute=True, autofill=False, cli_name='hostca
 option: StrEnum('cmdcategory', attribute=True, autofill=False, cli_name='cmdcat', multivalue=False, query=True, required=False, values=(u'all',))
 option: StrEnum('ipasudorunasusercategory', attribute=True, autofill=False, cli_name='runasusercat', multivalue=False, query=True, required=False, values=(u'all',))
 option: StrEnum('ipasudorunasgroupcategory', attribute=True, autofill=False, cli_name='runasgroupcat', multivalue=False, query=True, required=False, values=(u'all',))
+option: Int('sudoorder', attribute=True, autofill=False, cli_name='order', default=0, multivalue=False, query=True, required=False)
 option: Str('externaluser', attribute=True, autofill=False, cli_name='externaluser', multivalue=False, query=True, required=False)
 option: Str('ipasudorunasextuser', attribute=True, autofill=False, cli_name='runasexternaluser', multivalue=False, query=True, required=False)
 option: Str('ipasudorunasextgroup', attribute=True, autofill=False, cli_name='runasexternalgroup', multivalue=False, query=True, required=False)
@@ -2959,7 +2961,7 @@ output: ListOfEntries('result', (<type 'list'>, <type 'tuple'>), Gettext('A list
 output: Output('count', <type 'int'>, None)
 output: Output('truncated', <type 'bool'>, None)
 command: sudorule_mod
-args: 1,16,3
+args: 1,17,3
 arg: Str('cn', attribute=True, cli_name='sudorule_name', multivalue=False, primary_key=True, query=True, required=True)
 option: Str('description', attribute=True, autofill=False, cli_name='desc', multivalue=False, required=False)
 option: StrEnum('usercategory', attribute=True, autofill=False, cli_name='usercat', multivalue=False, required=False, values=(u'all',))
@@ -2967,6 +2969,7 @@ option: StrEnum('hostcategory', attribute=True, autofill=False, cli_name='hostca
 option: StrEnum('cmdcategory', attribute=True, autofill=False, cli_name='cmdcat', multivalue=False, required=False, values=(u'all',))
 option: StrEnum('ipasudorunasusercategory', attribute=True, autofill=False, cli_name='runasusercat', multivalue=False, required=False, values=(u'all',))
 option: StrEnum('ipasudorunasgroupcategory', attribute=True, autofill=False, cli_name='runasgroupcat', multivalue=False, required=False, values=(u'all',))
+option: Int('sudoorder', attribute=True, autofill=False, cli_name='order', default=0, multivalue=False, required=False)
 option: Str('externaluser', attribute=True, autofill=False, cli_name='externaluser', multivalue=False, required=False)
 option: Str('ipasudorunasextuser', attribute=True, autofill=False, cli_name='runasexternaluser', multivalue=False, required=False)
 option: Str('ipasudorunasextgroup', attribute=True, autofill=False, cli_name='runasexternalgroup', multivalue=False, required=False)
diff --git a/install/share/60ipasudo.ldif b/install/share/60ipasudo.ldif
deleted file mode 100644
index 61c73c0840a6ec9f987db8b892d0588b7f25679f..0000000000000000000000000000000000000000
--- a/install/share/60ipasudo.ldif
+++ /dev/null
@@ -1,39 +0,0 @@
-dn: cn=schema
-##
-## IPA SUDO schema (added in IPA v2)
-##
-## Attributes:          2.16.840.1.113730.3.8.7.x
-## ObjectClasses:       2.16.840.1.113730.3.8.8.x
-##
-## Attribute to store DN of an allowed SUDO command or a group of SUDO commands
-attributetypes: (2.16.840.1.113730.3.8.7.1 NAME 'memberAllowCmd' DESC 'Reference to a command or group of commands that are allowed by the rule.' SUP distinguishedName EQUALITY distinguishedNameMatch ORDERING distinguishedNameMatch SUBSTR distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'IPA v2' )
-## Attribute to store DN of a prohibited SUDO command or a group of SUDO commands
-attributetypes: (2.16.840.1.113730.3.8.7.2 NAME 'memberDenyCmd' DESC 'Reference to a command or group of commands that are denied by the rule.' SUP distinguishedName EQUALITY distinguishedNameMatch ORDERING distinguishedNameMatch SUBSTR distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'IPA v2' )
-## Attribute to store command category
-attributeTypes: (2.16.840.1.113730.3.8.7.3 NAME 'cmdCategory' DESC 'Additional classification for commands' EQUALITY caseIgnoreMatch ORDERING caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
-## Attribute to store user not managed by the central server
-attributetypes: (2.16.840.1.113730.3.8.7.4 NAME 'externalUser' DESC 'Multivalue string attribute that allows storing user names.' EQUALITY caseIgnoreMatch ORDERING caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
-## Attribute to store sudo options
-attributetypes: (2.16.840.1.113730.3.8.7.5 NAME 'ipaSudoOpt' DESC 'Options(s) followed by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'IPA v2' )
-## Attribute to store the reference identity under which the command should be run
-attributeTypes: (2.16.840.1.113730.3.8.7.6 NAME 'ipaSudoRunAs' DESC 'Reference to a user or group that the commands can be run as.' SUP memberUser X-ORIGIN 'IPA v2' )
-## Attribute to store a name of the user not managed by IPA. Command witll be executed under his identity.
-attributeTypes: (2.16.840.1.113730.3.8.7.7 NAME 'ipaSudoRunAsExtUser' DESC 'Multivalue string attribute that allows storing user name the command can be run as' EQUALITY caseIgnoreMatch ORDERING caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
-## Attribute to express category of identities that the command can be run under
-attributeTypes: (2.16.840.1.113730.3.8.7.8 NAME 'ipaSudoRunAsUserCategory' DESC 'Additional classification for users' SUP userCategory X-ORIGIN 'IPA v2' )
-## Attribute to store a reference to the "run as group" identitity
-attributeTypes: (2.16.840.1.113730.3.8.7.9 NAME 'ipaSudoRunAsGroup' DESC 'Reference to group that the commands can be run as.' SUP memberUser X-ORIGIN 'IPA v2' )
-## Attribute to store a name of the "run as group" identitity if this group is not directly managed by IPA
-attributeTypes: (2.16.840.1.113730.3.8.7.10 NAME 'ipaSudoRunAsExtGroup' DESC 'Multivalue string attribute that allows storing group name the command can be run as' EQUALITY caseIgnoreMatch ORDERING caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
-## Attribute to express category of group identities that the command can be run under
-attributeTypes: (2.16.840.1.113730.3.8.7.11 NAME 'ipaSudoRunAsGroupCategory' DESC 'Additional classification for groups' SUP userCategory X-ORIGIN 'IPA v2' )
-## Attribute to store host mask
-attributeTypes: (2.16.840.1.113730.3.8.7.12 NAME 'hostMask' DESC 'IP mask to identify a subnet.' EQUALITY caseIgnoreMatch ORDERING caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
-## Attribute to store sudo command
-attributeTypes: (2.16.840.1.113730.3.8.7.13 NAME 'sudoCmd' DESC 'Command(s) to be executed by sudo' EQUALITY caseExactMatch ORDERING caseExactMatch SUBSTR caseExactSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
-## Object class for SUDO rules
-objectClasses: (2.16.840.1.113730.3.8.8.1 NAME 'ipaSudoRule' SUP ipaAssociation STRUCTURAL MAY ( externalUser $ externalHost $ hostMask $ memberAllowCmd $ memberDenyCmd $ cmdCategory $ ipaSudoOpt $ ipaSudoRunAs $ ipaSudoRunAsExtUser $ ipaSudoRunAsUserCategory $ ipaSudoRunAsGroup $ ipaSudoRunAsExtGroup $ ipaSudoRunAsGroupCategory ) X-ORIGIN 'IPA v2' )
-## Object class for SUDO commands
-objectClasses: (2.16.840.1.113730.3.8.8.2 NAME 'ipaSudoCmd' DESC 'IPA object class for SUDO command' STRUCTURAL MUST ( ipaUniqueID $ sudoCmd ) MAY  ( memberOf $ description ) X-ORIGIN 'IPA v2' )
-## Object class for groups of the SUDO commands
-objectClasses: (2.16.840.1.113730.3.8.8.3 NAME 'ipaSudoCmdGrp' DESC 'IPA object class to store groups of SUDO commands' SUP groupOfNames MUST ( ipaUniqueID ) STRUCTURAL X-ORIGIN 'IPA v2' )
diff --git a/install/share/65ipasudo.ldif b/install/share/65ipasudo.ldif
new file mode 100644
index 0000000000000000000000000000000000000000..7a85c8659c33794d3127d208452dcb54ad34d59e
--- /dev/null
+++ b/install/share/65ipasudo.ldif
@@ -0,0 +1,39 @@
+dn: cn=schema
+##
+## IPA SUDO schema (added in IPA v2)
+##
+## Attributes:          2.16.840.1.113730.3.8.7.x
+## ObjectClasses:       2.16.840.1.113730.3.8.8.x
+##
+## Attribute to store DN of an allowed SUDO command or a group of SUDO commands
+attributetypes: (2.16.840.1.113730.3.8.7.1 NAME 'memberAllowCmd' DESC 'Reference to a command or group of commands that are allowed by the rule.' SUP distinguishedName EQUALITY distinguishedNameMatch ORDERING distinguishedNameMatch SUBSTR distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'IPA v2' )
+## Attribute to store DN of a prohibited SUDO command or a group of SUDO commands
+attributetypes: (2.16.840.1.113730.3.8.7.2 NAME 'memberDenyCmd' DESC 'Reference to a command or group of commands that are denied by the rule.' SUP distinguishedName EQUALITY distinguishedNameMatch ORDERING distinguishedNameMatch SUBSTR distinguishedNameMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 X-ORIGIN 'IPA v2' )
+## Attribute to store command category
+attributeTypes: (2.16.840.1.113730.3.8.7.3 NAME 'cmdCategory' DESC 'Additional classification for commands' EQUALITY caseIgnoreMatch ORDERING caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
+## Attribute to store user not managed by the central server
+attributetypes: (2.16.840.1.113730.3.8.7.4 NAME 'externalUser' DESC 'Multivalue string attribute that allows storing user names.' EQUALITY caseIgnoreMatch ORDERING caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
+## Attribute to store sudo options
+attributetypes: (2.16.840.1.113730.3.8.7.5 NAME 'ipaSudoOpt' DESC 'Options(s) followed by sudo' EQUALITY caseExactIA5Match SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 X-ORIGIN 'IPA v2' )
+## Attribute to store the reference identity under which the command should be run
+attributeTypes: (2.16.840.1.113730.3.8.7.6 NAME 'ipaSudoRunAs' DESC 'Reference to a user or group that the commands can be run as.' SUP memberUser X-ORIGIN 'IPA v2' )
+## Attribute to store a name of the user not managed by IPA. Command witll be executed under his identity.
+attributeTypes: (2.16.840.1.113730.3.8.7.7 NAME 'ipaSudoRunAsExtUser' DESC 'Multivalue string attribute that allows storing user name the command can be run as' EQUALITY caseIgnoreMatch ORDERING caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
+## Attribute to express category of identities that the command can be run under
+attributeTypes: (2.16.840.1.113730.3.8.7.8 NAME 'ipaSudoRunAsUserCategory' DESC 'Additional classification for users' SUP userCategory X-ORIGIN 'IPA v2' )
+## Attribute to store a reference to the "run as group" identitity
+attributeTypes: (2.16.840.1.113730.3.8.7.9 NAME 'ipaSudoRunAsGroup' DESC 'Reference to group that the commands can be run as.' SUP memberUser X-ORIGIN 'IPA v2' )
+## Attribute to store a name of the "run as group" identitity if this group is not directly managed by IPA
+attributeTypes: (2.16.840.1.113730.3.8.7.10 NAME 'ipaSudoRunAsExtGroup' DESC 'Multivalue string attribute that allows storing group name the command can be run as' EQUALITY caseIgnoreMatch ORDERING caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
+## Attribute to express category of group identities that the command can be run under
+attributeTypes: (2.16.840.1.113730.3.8.7.11 NAME 'ipaSudoRunAsGroupCategory' DESC 'Additional classification for groups' SUP userCategory X-ORIGIN 'IPA v2' )
+## Attribute to store host mask
+attributeTypes: (2.16.840.1.113730.3.8.7.12 NAME 'hostMask' DESC 'IP mask to identify a subnet.' EQUALITY caseIgnoreMatch ORDERING caseIgnoreMatch SUBSTR caseIgnoreSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
+## Attribute to store sudo command
+attributeTypes: (2.16.840.1.113730.3.8.7.13 NAME 'sudoCmd' DESC 'Command(s) to be executed by sudo' EQUALITY caseExactMatch ORDERING caseExactMatch SUBSTR caseExactSubstringsMatch SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 X-ORIGIN 'IPA v2' )
+## Object class for SUDO rules
+objectClasses: (2.16.840.1.113730.3.8.8.1 NAME 'ipaSudoRule' SUP ipaAssociation STRUCTURAL MAY ( externalUser $ externalHost $ hostMask $ memberAllowCmd $ memberDenyCmd $ cmdCategory $ ipaSudoOpt $ ipaSudoRunAs $ ipaSudoRunAsExtUser $ ipaSudoRunAsUserCategory $ ipaSudoRunAsGroup $ ipaSudoRunAsExtGroup $ ipaSudoRunAsGroupCategory $ sudoNotBefore $ sudoNotAfter $$ sudoOrder ) X-ORIGIN 'IPA v2' )
+## Object class for SUDO commands
+objectClasses: (2.16.840.1.113730.3.8.8.2 NAME 'ipaSudoCmd' DESC 'IPA object class for SUDO command' STRUCTURAL MUST ( ipaUniqueID $ sudoCmd ) MAY  ( memberOf $ description ) X-ORIGIN 'IPA v2' )
+## Object class for groups of the SUDO commands
+objectClasses: (2.16.840.1.113730.3.8.8.3 NAME 'ipaSudoCmdGrp' DESC 'IPA object class to store groups of SUDO commands' SUP groupOfNames MUST ( ipaUniqueID ) STRUCTURAL X-ORIGIN 'IPA v2' )
diff --git a/install/share/Makefile.am b/install/share/Makefile.am
index c33e0a5454c5bf2f7ff7b841ad14f2d4a4ca5737..31e4455d4c40997832ea0dffcbe5c0fce5fcbf09 100644
--- a/install/share/Makefile.am
+++ b/install/share/Makefile.am
@@ -9,7 +9,7 @@ app_DATA =				\
 	60basev2.ldif			\
 	60basev3.ldif			\
 	60ipadns.ldif			\
-	60ipasudo.ldif			\
+	65ipasudo.ldif			\
 	anonymous-vlv.ldif		\
 	bootstrap-template.ldif		\
 	caJarSigningCert.cfg.template	\
diff --git a/install/updates/10-sudo.update b/install/updates/10-sudo.update
index 88bdc3ce111db32866461a59da5830f9c2bb19ca..a12da00434cee1b49147586f54139ecc12d99e64 100644
--- a/install/updates/10-sudo.update
+++ b/install/updates/10-sudo.update
@@ -38,3 +38,5 @@ add:attributeTypes: ( 1.3.6.1.4.1.15953.9.1.10
      SYNTAX 1.3.6.1.4.1.1466.115.121.1.27
      X-ORIGIN 'SUDO' )
 replace:objectClasses:( 1.3.6.1.4.1.15953.9.2.1 NAME 'sudoRole' DESC 'Sudoer Entries' STRUCTURAL MUST cn MAY ( sudoUser $$ sudoHost $$ sudoCommand $$ sudoRunAs $$ sudoOption $$ description ) X-ORIGIN 'SUDO' )::( 1.3.6.1.4.1.15953.9.2.1 NAME 'sudoRole' SUP top STRUCTURAL DESC 'Sudoer Entries' MUST ( cn ) MAY ( sudoUser $$ sudoHost $$ sudoCommand $$ sudoRunAs $$ sudoRunAsUser $$ sudoRunAsGroup $$ sudoOption $$ sudoNotBefore $$ sudoNotAfter $$ sudoOrder $$ description ) X-ORIGIN 'SUDO')
+
+replace:objectClasses: ( 2.16.840.1.113730.3.8.8.1 NAME 'ipaSudoRule' SUP ipaAssociation STRUCTURAL MAY ( externalUser $$ externalHost $$ hostMask $$ memberAllowCmd $$ memberDenyCmd $$ cmdCategory $$ ipaSudoOpt $$ ipaSudoRunAs $$ ipaSudoRunAsExtUser $$ ipaSudoRunAsUserCategory $$ ipaSudoRunAsGroup $$ ipaSudoRunAsExtGroup $$ ipaSudoRunAsGroupCategory ) X-ORIGIN 'IPA v2' )::(2.16.840.1.113730.3.8.8.1 NAME 'ipaSudoRule' SUP ipaAssociation STRUCTURAL MAY ( externalUser $$ externalHost $$ hostMask $$ memberAllowCmd $$ memberDenyCmd $$ cmdCategory $$ ipaSudoOpt $$ ipaSudoRunAs $$ ipaSudoRunAsExtUser $$ ipaSudoRunAsUserCategory $$ ipaSudoRunAsGroup $$ ipaSudoRunAsExtGroup $$ ipaSudoRunAsGroupCategory $$ sudoNotBefore $$ sudoNotAfter $$ sudoOrder) X-ORIGIN 'IPA v2' )
diff --git a/ipalib/plugins/sudorule.py b/ipalib/plugins/sudorule.py
index ff7b756a3b27c089b9718c72ed93f13dfa3fb44a..1bf78f93708fd3b36d18646f0baf58593afacb38 100644
--- a/ipalib/plugins/sudorule.py
+++ b/ipalib/plugins/sudorule.py
@@ -40,6 +40,10 @@ FreeIPA provides a means to configure the various aspects of Sudo:
    RunAsGroup: The group(s) whose gid rights Sudo will be invoked with.
    Options: The various Sudoers Options that can modify Sudo's behavior.
 
+An order can be added to a sudorule to control the order in which they
+are evaluated (if the client supports it). This order is an integer and
+must be unique.
+
 FreeIPA provides a designated binddn to use with Sudo located at:
 uid=sudo,cn=sysaccounts,cn=etc,dc=example,dc=com
 
@@ -80,6 +84,7 @@ class sudorule(LDAPObject):
         'memberallowcmd', 'memberdenycmd', 'ipasudoopt',
         'ipasudorunas', 'ipasudorunasgroup',
         'ipasudorunasusercategory', 'ipasudorunasgroupcategory',
+        'sudoorder',
     ]
     uuid_attribute = 'ipauniqueid'
     rdn_attribute = 'ipauniqueid'
@@ -139,6 +144,12 @@ class sudorule(LDAPObject):
             doc=_('RunAs Group category the rule applies to'),
             values=(u'all', ),
         ),
+        Int('sudoorder?',
+            cli_name='order',
+            label=_('Sudo order'),
+            doc=_('integer to order the Sudo rules'),
+            default=0,
+        ),
         Str('memberuser_user?',
             label=_('Users'),
             flags=['no_create', 'no_update', 'no_search'],
@@ -207,6 +218,25 @@ class sudorule(LDAPObject):
         ),
     )
 
+    order_not_unique_msg = _(
+        'order must be a unique value (%(order)d already used by %(rule)s)'
+    )
+
+    def check_order_uniqueness(self, *keys, **options):
+        if 'sudoorder' in options:
+            entries = self.methods.find(
+                sudoorder=options['sudoorder']
+            )['result']
+            if len(entries) > 0:
+                rule_name = entries[0]['cn'][0]
+                raise errors.ValidationError(
+                    name='order',
+                    error=self.order_not_unique_msg % {
+                        'order': options['sudoorder'],
+                        'rule': rule_name,
+                    }
+                )
+
 api.register(sudorule)
 
 
@@ -214,6 +244,7 @@ class sudorule_add(LDAPCreate):
     __doc__ = _('Create new Sudo Rule.')
 
     def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+        self.obj.check_order_uniqueness(*keys, **options)
         # Sudo Rules are enabled by default
         entry_attrs['ipaenabledflag'] = 'TRUE'
         return dn
@@ -236,6 +267,15 @@ class sudorule_mod(LDAPUpdate):
 
     msg_summary = _('Modified Sudo Rule "%(value)s"')
     def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys, **options):
+        if 'sudoorder' in options:
+            new_order = options.get('sudoorder')
+            old_entry = self.api.Command.sudorule_show(keys[-1])['result']
+            if 'sudoorder' in old_entry:
+                old_order = int(old_entry['sudoorder'][0])
+                if old_order != new_order:
+                    self.obj.check_order_uniqueness(*keys, **options)
+            else:
+                self.obj.check_order_uniqueness(*keys, **options)
         try:
             (_dn, _entry_attrs) = ldap.get_entry(dn, self.obj.default_attributes)
         except errors.NotFound:
diff --git a/ipaserver/install/dsinstance.py b/ipaserver/install/dsinstance.py
index 5b5b24caab187c2a5ce874fd04ab3cccd7997311..e549e13ccad711da8fbde359f9263f5828c6d5a0 100644
--- a/ipaserver/install/dsinstance.py
+++ b/ipaserver/install/dsinstance.py
@@ -359,7 +359,7 @@ class DsInstance(service.Service):
                              "60basev2.ldif",
                              "60basev3.ldif",
                              "60ipadns.ldif",
-                             "60ipasudo.ldif"):
+                             "65ipasudo.ldif"):
             target_fname = schema_dirname(self.serverid) + schema_fname
             shutil.copyfile(ipautil.SHARE_DIR + schema_fname, target_fname)
             os.chmod(target_fname, 0440)    # read access for dirsrv user/group
diff --git a/tests/test_xmlrpc/test_sudorule_plugin.py b/tests/test_xmlrpc/test_sudorule_plugin.py
index 4e283c20859ed0bbb83ac783ef7a80e9fa19fdca..bddf00c4f28ebcb1fe64839a84b1ab366f39185b 100644
--- a/tests/test_xmlrpc/test_sudorule_plugin.py
+++ b/tests/test_xmlrpc/test_sudorule_plugin.py
@@ -30,6 +30,7 @@ class test_sudorule(XMLRPC_test):
     Test the `sudorule` plugin.
     """
     rule_name = u'testing_sudorule1'
+    rule_name2 = u'testing_sudorule2'
     rule_command = u'/usr/bin/testsudocmd1'
     rule_desc = u'description'
     rule_desc_mod = u'description modified'
@@ -38,8 +39,8 @@ class test_sudorule(XMLRPC_test):
     test_external_user = u'external_test_user'
     test_group = u'sudorule_test_group'
     test_external_group = u'external_test_group'
-    test_host = u'sudorule.test-host'
-    test_external_host = u'external.test-host'
+    test_host = u'sudorule.testhost'
+    test_external_host = u'external.testhost'
     test_hostgroup = u'sudorule_test_hostgroup'
     test_sudoallowcmdgroup = u'sudorule_test_allowcmdgroup'
     test_sudodenycmdgroup = u'sudorule_test_denycmdgroup'
@@ -625,8 +626,45 @@ class test_sudorule(XMLRPC_test):
         api.Command['sudocmdgroup_del'](self.test_sudoallowcmdgroup)
         api.Command['sudocmdgroup_del'](self.test_sudodenycmdgroup)
 
+    def test_l_sudorule_order(self):
+        """
+        Test that order uniqueness is maintained
+        """
+        api.Command['sudorule_mod'](self.rule_name, sudoorder=1)
+
+        api.Command['sudorule_add'](self.rule_name2)
+
+        # mod of rule that has no order and set a duplicate
+        try:
+            api.Command['sudorule_mod'](self.rule_name2, sudoorder=1)
+        except errors.ValidationError:
+            pass
+
+        # Remove the rule so we can re-add it
+        api.Command['sudorule_del'](self.rule_name2)
+
+        # add a new rule with a duplicate order
+        try:
+            api.Command['sudorule_add'](self.rule_name2, sudoorder=1)
+        except errors.ValidationError:
+            pass
+
+        # add a new rule with a unique order
+        api.Command['sudorule_add'](self.rule_name2, sudoorder=2)
+        try:
+            api.Command['sudorule_mod'](self.rule_name2, sudoorder=1)
+        except errors.ValidationError:
+            pass
+
+        # Try setting both to 0
+        api.Command['sudorule_mod'](self.rule_name2, sudoorder=0)
+        try:
+            api.Command['sudorule_mod'](self.rule_name, sudoorder=0)
+        except errors.ValidationError:
+            pass
+
 
-    def test_l_sudorule_del(self):
+    def test_m_sudorule_del(self):
         """
         Test deleting a Sudo rule using `xmlrpc.sudorule_del`.
         """
@@ -638,3 +676,4 @@ class test_sudorule(XMLRPC_test):
             pass
         else:
             assert False
+        api.Command['sudorule_del'](self.rule_name2)
-- 
1.7.6

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to