Give ipa-adduser, ipa-addgroup and ipa-usermod an interactive mode

Add ipa-passwd tool
Add simple field validation package
This patch adds a package requirement, python-krbV. This is needed to
 determine the current user based on their kerberos ticket.
This commit is contained in:
rcritten@redhat.com 2007-09-21 10:24:36 -04:00
parent 919d037189
commit 7b96973711
9 changed files with 599 additions and 33 deletions

View File

@ -11,6 +11,7 @@ install:
install -m 755 ipa-delgroup $(SBINDIR) install -m 755 ipa-delgroup $(SBINDIR)
install -m 755 ipa-findgroup $(SBINDIR) install -m 755 ipa-findgroup $(SBINDIR)
install -m 755 ipa-groupmod $(SBINDIR) install -m 755 ipa-groupmod $(SBINDIR)
install -m 755 ipa-passwd $(SBINDIR)
clean: clean:
rm -f *~ *.pyc rm -f *~ *.pyc

View File

@ -23,6 +23,7 @@ from optparse import OptionParser
import ipa import ipa
import ipa.group import ipa.group
import ipa.ipaclient as ipaclient import ipa.ipaclient as ipaclient
import ipa.ipavalidate as ipavalidate
import ipa.config import ipa.config
import ipa.ipaerror import ipa.ipaerror
@ -49,22 +50,51 @@ def parse_options():
return options, args return options, args
def main(): def main():
cn = ""
desc = ""
group=ipa.group.Group() group=ipa.group.Group()
options, args = parse_options() options, args = parse_options()
if len(args) != 2: cont = False
usage()
if (len(args) != 2):
while (cont != True):
cn = raw_input("Group name: ")
if (ipavalidate.plain(cn, notEmpty=True)):
print "Field is required and must be letters or '."
else:
cont = True
else:
cn = args[1]
if (ipavalidate.plain(cn, notEmpty=True)):
print "Group name is required and must be letters or '."
return 1
cont = False
if not options.desc:
while (cont != True):
desc = raw_input("Description: ")
if (ipavalidate.plain(desc, notEmpty=True)):
print "Field is required and must be letters or '."
else:
cont = True
else:
desc = options.desc
if (ipavalidate.plain(desc, notEmpty=True)):
print "First name is required and must be letters or '."
return 1
group.setValue('cn', args[1])
if options.desc:
group.setValue('description', options.desc)
if options.gid: if options.gid:
group.setValue('gidnumber', options.gid) group.setValue('gidnumber', options.gid)
group.setValue('cn', cn)
group.setValue('description', desc)
try: try:
client = ipaclient.IPAClient() client = ipaclient.IPAClient()
client.add_group(group) client.add_group(group)
print args[1] + " successfully added" print cn + " successfully added"
except xmlrpclib.Fault, f: except xmlrpclib.Fault, f:
print f.faultString print f.faultString
return 1 return 1

View File

@ -23,11 +23,13 @@ from optparse import OptionParser
import ipa import ipa
import ipa.user import ipa.user
import ipa.ipaclient as ipaclient import ipa.ipaclient as ipaclient
import ipa.ipavalidate as ipavalidate
import ipa.config import ipa.config
import xmlrpclib import xmlrpclib
import kerberos import kerberos
import ldap import ldap
import getpass
def usage(): def usage():
print "ipa-adduser [-c|--gecos STRING] [-d|--directory STRING] [-f|--firstname STRING] [-l|--lastname STRING] user" print "ipa-adduser [-c|--gecos STRING] [-d|--directory STRING] [-f|--firstname STRING] [-l|--lastname STRING] user"
@ -47,37 +49,168 @@ def parse_options():
help="Set user's password") help="Set user's password")
parser.add_option("-s", "--shell", dest="shell", parser.add_option("-s", "--shell", dest="shell",
help="Set user's login shell to shell") help="Set user's login shell to shell")
parser.add_option("-G", "--groups", dest="groups",
help="Add account to one or more groups (comma-separated)")
parser.add_option("-M", "--mailAddress", dest="mail",
help="Set uesr's e-mail address")
parser.add_option("--usage", action="store_true", parser.add_option("--usage", action="store_true",
help="Program usage") help="Program usage")
args = ipa.config.init_config(sys.argv) args = ipa.config.init_config(sys.argv)
options, args = parser.parse_args(args) options, args = parser.parse_args(args)
if not options.gn or not options.sn:
usage()
return options, args return options, args
def main(): def main():
# The following fields are required
givenname = ""
lastname = ""
username = ""
password = ""
mail = ""
gecos = ""
directory = ""
shell = ""
groups = ""
match = False
cont = False
all_interactive = False
user=ipa.user.User() user=ipa.user.User()
options, args = parse_options() options, args = parse_options()
if len(args) != 2: if len(args) != 2:
usage() all_interactive = True
user.setValue('givenname', options.gn) if not options.gn:
user.setValue('sn', options.sn) while (cont != True):
user.setValue('uid', args[1]) givenname = raw_input("First name: ")
if options.gecos: if (ipavalidate.plain(givenname, notEmpty=True)):
user.setValue('gecos', options.gecos) print "Field is required and must be letters or '"
if options.directory: else:
user.setValue('homedirectory', options.directory) cont = True
if options.shell:
user.setValue('loginshell', options.shell)
else: else:
user.setValue('loginshell', "/bin/bash") givenname = options.gn
if (ipavalidate.plain(givenname, notEmpty=True)):
print "First name is required and must be letters or '"
return 1
username = args[1] cont = False
if not options.sn:
while (cont != True):
lastname = raw_input(" Last name: ")
if (ipavalidate.plain(lastname, notEmpty=True)):
print "Field is required and must be letters or '"
else:
cont = True
else:
lastname = options.sn
if (ipavalidate.plain(lastname, notEmpty=True)):
print "Last name is required and must be letters or '"
return 1
cont = False
if (len(args) != 2):
while (cont != True):
username = raw_input("Login name: ")
if (ipavalidate.plain(username, notEmpty=True)):
print "Field is required and must be letters or '"
else:
cont = True
else:
username = args[1]
if (ipavalidate.plain(username, notEmpty=True)):
print "Username is required and must be letters or '"
return 1
if not options.password:
while (match != True):
password = getpass.getpass(" Password: ")
confirm = getpass.getpass(" Password (again): ")
if (password != confirm):
print "Passwords do not match"
match = False
else:
match = True
if (len(password) < 1):
print "Password cannot be empty"
match = False
else:
password = options.sn
cont = False
if not options.mail:
while (cont != True):
mail = raw_input("E-mail addr: ")
if (ipavalidate.email(mail)):
print "Field is required and must include a user and domain name"
else:
cont = True
else:
mail = options.mail
if (ipavalidate.email(mail)):
print "E-mail is required and must include a user and domain name"
return 1
# Ask the questions we don't normally force. We don't require answers
# for these.
if all_interactive is True:
cont = False
if not options.gecos:
while (cont != True):
gecos = raw_input("gecos []: ")
if (ipavalidate.plain(gecos, notEmpty=False)):
print "Must be letters, numbers, spaces or '"
else:
cont = True
cont = False
if not options.directory:
while (cont != True):
directory = raw_input("home directory []: ")
if (ipavalidate.path(gecos, notEmpty=False)):
print "Must be letters, numbers, spaces or '"
else:
cont = True
cont = False
if not options.shell:
while (cont != True):
shell = raw_input("shell [/bin/sh]: ")
if len(shell) < 1:
shell = None
cont = True
cont = False
if not options.groups:
while (cont != True):
g = raw_input("Add to group [blank to exit]: ")
if len(g) < 1:
cont = True
else:
if (ipavalidate.path(g, notEmpty=False)):
print "Must be letters, numbers, spaces or '"
else:
groups = groups + "," + g
else:
gecos = options.gecos
directory = options.directory
shell = options.shell
groups = options.groups
user.setValue('givenname', givenname)
user.setValue('sn', lastname)
user.setValue('uid', username)
user.setValue('mail', mail)
if gecos:
user.setValue('gecos', gecos)
if directory:
user.setValue('homedirectory', directory)
if shell:
user.setValue('loginshell', shell)
else:
user.setValue('loginshell', "/bin/sh")
try: try:
client = ipaclient.IPAClient() client = ipaclient.IPAClient()
@ -95,13 +228,26 @@ def main():
print "%s" % (e.message) print "%s" % (e.message)
return 1 return 1
if options.password is not None: # Set the User's password
if password is not None:
try: try:
client.modifyPassword(username, None, options.password) client.modifyPassword(username, None, password)
except ipa.ipaerror.IPAError, e: except ipa.ipaerror.IPAError, e:
print "User added but setting the password failed."
print "%s" % (e.message) print "%s" % (e.message)
return 1 return 1
# Add to any groups
if groups:
add_groups = groups.split(',')
for g in add_groups:
if g:
try:
client.add_user_to_group(username, g)
print "%s added to group %s" % (username, g)
except ipa.ipaerror.exception_for(ipa.ipaerror.LDAP_NOT_FOUND):
print "group %s doesn't exist, skipping" % g
print username + " successfully added" print username + " successfully added"
return 0 return 0

99
ipa-admintools/ipa-passwd Normal file
View File

@ -0,0 +1,99 @@
#! /usr/bin/python -E
# Authors: Rob Crittenden <rcritten@redhat.com>
#
# Copyright (C) 2007 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 only
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Tempal Place, Suite 330, Boston, MA 02111-1307 USA
#
import sys
from optparse import OptionParser
import ipa
import ipa.ipaclient as ipaclient
import ipa.config
import xmlrpclib
import kerberos
import krbV
import ldap
import getpass
def usage():
print "ipa-passwd [user]"
sys.exit(1)
def parse_options():
parser = OptionParser()
parser.add_option("--usage", action="store_true",
help="Program usage")
args = ipa.config.init_config(sys.argv)
options, args = parser.parse_args(args)
return options, args
def get_principal():
try:
ctx = krbV.default_context()
ccache = ctx.default_ccache()
cprinc = ccache.principal()
except krbV.Krb5Error, e:
print "Unable to get kerberos principal: %s" % e[1]
return None
return cprinc.name
def main():
match = False
options, args = parse_options()
if len(args) == 2:
username = args[1]
else:
username = get_principal()
if username is None:
return 1
u = username.split('@')
if len(u) > 1:
username = u[0]
print "Changing password for %s" % username
while (match != True):
# No syntax checking of the password is required because that is done
# on the server side
password = getpass.getpass(" New Password: ")
confirm = getpass.getpass(" New Password (again): ")
if (password != confirm):
print "Passwords do not match"
match = False
else:
match = True
if (len(password) < 1):
print "Password cannot be empty"
match = False
try:
client = ipaclient.IPAClient()
client.modifyPassword(username, None, password)
except ipa.ipaerror.IPAError, e:
print "%s" % (e.message)
return 1
return 0
main()

View File

@ -21,14 +21,17 @@
import sys import sys
from optparse import OptionParser from optparse import OptionParser
import ipa import ipa
import ipa.user
import ipa.ipaclient as ipaclient import ipa.ipaclient as ipaclient
import ipa.ipavalidate as ipavalidate
import ipa.config import ipa.config
import xmlrpclib import xmlrpclib
import kerberos import kerberos
import ldap
def usage(): def usage():
print "ipa-usermod [-c|--gecos STRING] [-d|--directory STRING] user" print "ipa-usermod [-c|--gecos STRING] [-d|--directory STRING] [-f|--firstname STRING] [-l|--lastname STRING] user"
sys.exit(1) sys.exit(1)
def parse_options(): def parse_options():
@ -37,8 +40,14 @@ def parse_options():
help="Set the GECOS field") help="Set the GECOS field")
parser.add_option("-d", "--directory", dest="directory", parser.add_option("-d", "--directory", dest="directory",
help="Set the User's home directory") help="Set the User's home directory")
parser.add_option("-f", "--firstname", dest="gn",
help="User's first name")
parser.add_option("-l", "--lastname", dest="sn",
help="User's last name")
parser.add_option("-s", "--shell", dest="shell", parser.add_option("-s", "--shell", dest="shell",
help="Set user's login shell to shell") help="Set user's login shell to shell")
parser.add_option("-M", "--mailAddress", dest="mail",
help="Set uesr's e-mail address")
parser.add_option("--usage", action="store_true", parser.add_option("--usage", action="store_true",
help="Program usage") help="Program usage")
@ -48,14 +57,32 @@ def parse_options():
return options, args return options, args
def main(): def main():
# The following fields are required
givenname = ""
lastname = ""
username = ""
mail = ""
gecos = ""
directory = ""
groups = ""
shell = ""
match = False
cont = False
options, args = parse_options() options, args = parse_options()
if len(args) != 2: if len(args) != 2:
usage() usage()
username = args[1]
client = ipaclient.IPAClient() client = ipaclient.IPAClient()
try: try:
user = client.get_user_by_uid(args[1]) user = client.get_user_by_uid(username)
except ipa.ipaerror.exception_for(ipa.ipaerror.LDAP_NOT_FOUND):
print "User %s not found" % username
return 1
except ipa.ipaerror.IPAError, e: except ipa.ipaerror.IPAError, e:
print "%s" % e.message print "%s" % e.message
return 1 return 1
@ -63,16 +90,107 @@ def main():
print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0]) print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0])
return 1 return 1
if options.gecos: # If any options are set we use just those. Otherwise ask for all of them.
user.setValue('gecos', options.gecos) if options.gn or options.sn or options.directory or options.gecos or options.mail:
if options.directory: givenname = options.gn
user.setValue('homedirectory', options.directory) lastname = options.sn
if options.shell: gecos = options.gecos
user.setValue('loginshell', options.shell) directory = options.directory
mail = options.mail
else:
if not options.gn:
while (cont != True):
givenname = raw_input("First name: [%s] " % user.getValue('givenname'))
if (ipavalidate.plain(givenname, notEmpty=False)):
print "Must be letters or '"
else:
cont = True
if len(givenname) < 1:
shell = None
cont = True
else:
givenname = options.gn
if (ipavalidate.plain(givenname, notEmpty=True)):
print "First name must be letters or '"
return 1
cont = False
if not options.sn:
while (cont != True):
lastname = raw_input(" Last name: [%s] " % user.getValue('sn'))
if (ipavalidate.plain(lastname, notEmpty=False)):
print "Must be letters or '"
else:
cont = True
if len(lastname) < 1:
shell = None
cont = True
else:
lastname = options.sn
if (ipavalidate.plain(lastname, notEmpty=True)):
print "Last name must be letters or '"
return 1
cont = False
if not options.mail:
while (cont != True):
mail = raw_input("E-mail addr: [%s]" % user.getValue('mail'))
if (ipavalidate.email(mail, notEmpty=False)):
print "Must include a user and domain name"
else:
cont = True
else:
mail = options.mail
if (ipavalidate.email(mail)):
print "E-mail must include a user and domain name"
return 1
# Ask the questions we don't normally force. We don't require answers
# for these.
cont = False
if not options.gecos:
while (cont != True):
gecos = raw_input("gecos: [%s] " % user.getValue('gecos'))
if (ipavalidate.plain(gecos, notEmpty=False)):
print "Must be letters, numbers, spaces or '"
else:
cont = True
cont = False
if not options.directory:
while (cont != True):
directory = raw_input("home directory: [%s] " % user.getValue('homeDirectory'))
if (ipavalidate.path(gecos, notEmpty=False)):
print "Must be letters, numbers, spaces or '"
else:
cont = True
cont = False
if not options.shell:
while (cont != True):
shell = raw_input("shell: [%s] " % user.getValue('loginshell'))
if len(shell) < 1:
shell = None
cont = True
cont = False
if givenname:
user.setValue('givenname', givenname)
if lastname:
user.setValue('sn', lastname)
if mail:
user.setValue('mail', mail)
user.setValue('cn', "%s %s" % (user.getValue('givenname'),
user.getValue('sn')))
if gecos:
user.setValue('gecos', gecos)
if directory:
user.setValue('homedirectory', directory)
if shell:
user.setValue('loginshell', shell)
try: try:
client.update_user(user) client.update_user(user)
print args[1] + " successfully modified"
except xmlrpclib.Fault, f: except xmlrpclib.Fault, f:
print f.faultString print f.faultString
return 1 return 1
@ -86,6 +204,7 @@ def main():
print "%s" % (e.message) print "%s" % (e.message)
return 1 return 1
print username + " successfully updated"
return 0 return 0
main() main()

View File

@ -10,7 +10,7 @@ Source0: %{name}-%{version}.tgz
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
BuildArch: noarch BuildArch: noarch
Requires: python PyKerberos Requires: python PyKerberos python-krbV
%{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")} %{!?python_sitelib: %define python_sitelib %(%{__python} -c "from distutils.sysconfig import get_python_lib; print get_python_lib()")}

99
ipa-python/ipavalidate.py Normal file
View File

@ -0,0 +1,99 @@
#! /usr/bin/python -E
# Authors: Rob Crittenden <rcritten@redhat.com>
#
# Copyright (C) 2007 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 or later
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
import re
def email(mail, notEmpty=True):
"""Do some basic validation of an e-mail address.
Return 0 if ok
Return 1 if not
If notEmpty is True the this will return an error if the field
is "" or None.
"""
usernameRE = re.compile(r"^[^ \t\n\r@<>()]+$", re.I)
domainRE = re.compile(r"^[a-z0-9][a-z0-9\.\-_]*\.[a-z]+$", re.I)
if not mail or mail is None:
if notEmpty is True:
return 1
else:
return 0
mail = mail.strip()
s = mail.split('@', 1)
try:
username, domain=s
except ValueError:
return 1
if not usernameRE.search(username):
return 1
if not domainRE.search(domain):
return 1
return 0
def plain(text, notEmpty=False):
"""Do some basic validation of a plain text field
Return 0 if ok
Return 1 if not
If notEmpty is True the this will return an error if the field
is "" or None.
"""
textRE = re.compile(r"^[a-zA-Z_\-0-9\'\ ]*$")
if not text and notEmpty is True:
return 1
if text is None:
if notEmpty is True:
return 1
else:
return 0
if not textRE.search(text):
return 1
return 0
def path(text, notEmpty=False):
"""Do some basic validation of a path
Return 0 if ok
Return 1 if not
If notEmpty is True the this will return an error if the field
is "" or None.
"""
textRE = re.compile(r"^[a-zA-Z_\-0-9\\ \.\/\\:]*$")
if not text and notEmpty is True:
return 1
if text is None:
if notEmpty is True:
return 1
else:
return 0
if not textRE.search(text):
return 1
return 0

View File

@ -0,0 +1,70 @@
#! /usr/bin/python -E
#
# Copyright (C) 2007 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; version 2 or later
#
# This program 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 General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#
import sys
sys.path.insert(0, ".")
import unittest
import ipavalidate
class TestValidate(unittest.TestCase):
def setUp(self):
pass
def tearDown(self):
pass
def test_validemail(self):
self.assertEqual(0, ipavalidate.email("test@freeipa.org"))
self.assertEqual(0, ipavalidate.email("", notEmpty=False))
def test_invalidemail(self):
self.assertEqual(1, ipavalidate.email("test"))
self.assertEqual(1, ipavalidate.email("test@freeipa"))
self.assertEqual(1, ipavalidate.email("test@.com"))
self.assertEqual(1, ipavalidate.email(""))
self.assertEqual(1, ipavalidate.email(None))
def test_validplain(self):
self.assertEqual(0, ipavalidate.plain("Joe User"))
self.assertEqual(0, ipavalidate.plain("Joe O'Malley"))
self.assertEqual(0, ipavalidate.plain("", notEmpty=False))
self.assertEqual(0, ipavalidate.plain(None, notEmpty=False))
def test_invalidplain(self):
self.assertEqual(1, ipavalidate.plain("Joe (User)"))
self.assertEqual(1, ipavalidate.plain("", notEmpty=True))
self.assertEqual(1, ipavalidate.plain(None, notEmpty=True))
def test_validpath(self):
self.assertEqual(0, ipavalidate.path("/"))
self.assertEqual(0, ipavalidate.path("/home/user"))
self.assertEqual(0, ipavalidate.path("../home/user"))
self.assertEqual(0, ipavalidate.path("", notEmpty=False))
self.assertEqual(0, ipavalidate.path(None, notEmpty=False))
def test_invalidpath(self):
self.assertEqual(1, ipavalidate.path("(foo)"))
self.assertEqual(1, ipavalidate.path("", notEmpty=True))
self.assertEqual(1, ipavalidate.path(None, notEmpty=True))
if __name__ == '__main__':
unittest.main()

View File

@ -19,6 +19,8 @@ mod_python
gcc gcc
python-ldap python-ldap
TurboGears TurboGears
PyKerberos
python-krbV
Installation example: Installation example: