mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
Merging with upstream
This commit is contained in:
commit
873bbbd2de
@ -7,6 +7,10 @@ install:
|
||||
install -m 755 ipa-finduser $(SBINDIR)
|
||||
install -m 755 ipa-usermod $(SBINDIR)
|
||||
install -m 755 ipa-deluser $(SBINDIR)
|
||||
install -m 755 ipa-addgroup $(SBINDIR)
|
||||
install -m 755 ipa-delgroup $(SBINDIR)
|
||||
install -m 755 ipa-findgroup $(SBINDIR)
|
||||
install -m 755 ipa-groupmod $(SBINDIR)
|
||||
|
||||
clean:
|
||||
rm -f *~ *.pyc
|
||||
|
@ -50,7 +50,9 @@ def main():
|
||||
client = ipaclient.IPAClient()
|
||||
users = client.find_users(args[1], sattrs=['dn','uid','cn','homeDirectory'])
|
||||
|
||||
if len(users) == 0:
|
||||
counter = users[0]
|
||||
users = users[1:]
|
||||
if counter == 0:
|
||||
print "No entries found for", args[1]
|
||||
return 0
|
||||
|
||||
|
@ -59,6 +59,9 @@ def main():
|
||||
except ipa.ipaerror.IPAError, e:
|
||||
print "%s" % e.message
|
||||
return 1
|
||||
except kerberos.GSSError, e:
|
||||
print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0])
|
||||
return 1
|
||||
|
||||
if options.gecos:
|
||||
user.setValue('gecos', options.gecos)
|
||||
@ -79,6 +82,9 @@ def main():
|
||||
except xmlrpclib.ProtocolError, e:
|
||||
print "Unable to connect to IPA server: %s" % (e.errmsg)
|
||||
return 1
|
||||
except ipa.ipaerror.IPAError, e:
|
||||
print "%s" % (e.message)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
@ -30,6 +30,7 @@ import stat
|
||||
from string import lower
|
||||
import re
|
||||
import xmlrpclib
|
||||
import datetime
|
||||
|
||||
def realm_to_suffix(realm_name):
|
||||
s = realm_name.split(".")
|
||||
@ -233,3 +234,100 @@ def unwrap_binary_data(data):
|
||||
else:
|
||||
return data
|
||||
|
||||
class GeneralizedTimeZone(datetime.tzinfo):
|
||||
"""This class is a basic timezone wrapper for the offset specified
|
||||
in a Generalized Time. It is dst-ignorant."""
|
||||
def __init__(self,offsetstr="Z"):
|
||||
super(GeneralizedTimeZone, self).__init__()
|
||||
|
||||
self.name = offsetstr
|
||||
self.houroffset = 0
|
||||
self.minoffset = 0
|
||||
|
||||
if offsetstr == "Z":
|
||||
self.houroffset = 0
|
||||
self.minoffset = 0
|
||||
else:
|
||||
if (len(offsetstr) >= 3) and re.match(r'[-+]\d\d', offsetstr):
|
||||
self.houroffset = int(offsetstr[0:3])
|
||||
offsetstr = offsetstr[3:]
|
||||
if (len(offsetstr) >= 2) and re.match(r'\d\d', offsetstr):
|
||||
self.minoffset = int(offsetstr[0:2])
|
||||
offsetstr = offsetstr[2:]
|
||||
if len(offsetstr) > 0:
|
||||
raise ValueError()
|
||||
if self.houroffset < 0:
|
||||
self.minoffset *= -1
|
||||
|
||||
def utcoffset(self, dt):
|
||||
return datetime.timedelta(hours=self.houroffset, minutes=self.minoffset)
|
||||
|
||||
def dst(self, dt):
|
||||
return datetime.timedelta(0)
|
||||
|
||||
def tzname(self, dt):
|
||||
return self.name
|
||||
|
||||
|
||||
def parse_generalized_time(timestr):
|
||||
"""Parses are Generalized Time string (as specified in X.680),
|
||||
returning a datetime object. Generalized Times are stored inside
|
||||
the krbPasswordExpiration attribute in LDAP.
|
||||
|
||||
This method doesn't attempt to be perfect wrt timezones. If python
|
||||
can't be bothered to implement them, how can we..."""
|
||||
|
||||
if len(timestr) < 8:
|
||||
return None
|
||||
try:
|
||||
date = timestr[:8]
|
||||
time = timestr[8:]
|
||||
|
||||
year = int(date[:4])
|
||||
month = int(date[4:6])
|
||||
day = int(date[6:8])
|
||||
|
||||
hour = min = sec = msec = 0
|
||||
tzone = None
|
||||
|
||||
if (len(time) >= 2) and re.match(r'\d', time[0]):
|
||||
hour = int(time[:2])
|
||||
time = time[2:]
|
||||
if len(time) >= 2 and (time[0] == "," or time[0] == "."):
|
||||
hour_fraction = "."
|
||||
time = time[1:]
|
||||
while (len(time) > 0) and re.match(r'\d', time[0]):
|
||||
hour_fraction += time[0]
|
||||
time = time[1:]
|
||||
total_secs = int(float(hour_fraction) * 3600)
|
||||
min, sec = divmod(total_secs, 60)
|
||||
|
||||
if (len(time) >= 2) and re.match(r'\d', time[0]):
|
||||
min = int(time[:2])
|
||||
time = time[2:]
|
||||
if len(time) >= 2 and (time[0] == "," or time[0] == "."):
|
||||
min_fraction = "."
|
||||
time = time[1:]
|
||||
while (len(time) > 0) and re.match(r'\d', time[0]):
|
||||
min_fraction += time[0]
|
||||
time = time[1:]
|
||||
sec = int(float(min_fraction) * 60)
|
||||
|
||||
if (len(time) >= 2) and re.match(r'\d', time[0]):
|
||||
sec = int(time[:2])
|
||||
time = time[2:]
|
||||
if len(time) >= 2 and (time[0] == "," or time[0] == "."):
|
||||
sec_fraction = "."
|
||||
time = time[1:]
|
||||
while (len(time) > 0) and re.match(r'\d', time[0]):
|
||||
sec_fraction += time[0]
|
||||
time = time[1:]
|
||||
msec = int(float(sec_fraction) * 1000000)
|
||||
|
||||
if (len(time) > 0):
|
||||
tzone = GeneralizedTimeZone(time)
|
||||
|
||||
return datetime.datetime(year, month, day, hour, min, sec, msec, tzone)
|
||||
|
||||
except ValueError:
|
||||
return None
|
||||
|
@ -21,6 +21,7 @@ import sys
|
||||
sys.path.insert(0, ".")
|
||||
|
||||
import unittest
|
||||
import datetime
|
||||
|
||||
import ipautil
|
||||
|
||||
@ -207,6 +208,102 @@ class TestCIDict(unittest.TestCase):
|
||||
self.assert_(item in items)
|
||||
items.discard(item)
|
||||
|
||||
class TestTimeParser(unittest.TestCase):
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
def testSimple(self):
|
||||
timestr = "20070803"
|
||||
|
||||
time = ipautil.parse_generalized_time(timestr)
|
||||
self.assertEqual(2007, time.year)
|
||||
self.assertEqual(8, time.month)
|
||||
self.assertEqual(3, time.day)
|
||||
self.assertEqual(0, time.hour)
|
||||
self.assertEqual(0, time.minute)
|
||||
self.assertEqual(0, time.second)
|
||||
|
||||
def testHourMinSec(self):
|
||||
timestr = "20051213141205"
|
||||
|
||||
time = ipautil.parse_generalized_time(timestr)
|
||||
self.assertEqual(2005, time.year)
|
||||
self.assertEqual(12, time.month)
|
||||
self.assertEqual(13, time.day)
|
||||
self.assertEqual(14, time.hour)
|
||||
self.assertEqual(12, time.minute)
|
||||
self.assertEqual(5, time.second)
|
||||
|
||||
def testFractions(self):
|
||||
timestr = "2003092208.5"
|
||||
|
||||
time = ipautil.parse_generalized_time(timestr)
|
||||
self.assertEqual(2003, time.year)
|
||||
self.assertEqual(9, time.month)
|
||||
self.assertEqual(22, time.day)
|
||||
self.assertEqual(8, time.hour)
|
||||
self.assertEqual(30, time.minute)
|
||||
self.assertEqual(0, time.second)
|
||||
|
||||
timestr = "199203301544,25"
|
||||
|
||||
time = ipautil.parse_generalized_time(timestr)
|
||||
self.assertEqual(1992, time.year)
|
||||
self.assertEqual(3, time.month)
|
||||
self.assertEqual(30, time.day)
|
||||
self.assertEqual(15, time.hour)
|
||||
self.assertEqual(44, time.minute)
|
||||
self.assertEqual(15, time.second)
|
||||
|
||||
timestr = "20060401185912,8"
|
||||
|
||||
time = ipautil.parse_generalized_time(timestr)
|
||||
self.assertEqual(2006, time.year)
|
||||
self.assertEqual(4, time.month)
|
||||
self.assertEqual(1, time.day)
|
||||
self.assertEqual(18, time.hour)
|
||||
self.assertEqual(59, time.minute)
|
||||
self.assertEqual(12, time.second)
|
||||
self.assertEqual(800000, time.microsecond)
|
||||
|
||||
def testTimeZones(self):
|
||||
timestr = "20051213141205Z"
|
||||
|
||||
time = ipautil.parse_generalized_time(timestr)
|
||||
self.assertEqual(0, time.tzinfo.houroffset)
|
||||
self.assertEqual(0, time.tzinfo.minoffset)
|
||||
offset = time.tzinfo.utcoffset(None)
|
||||
self.assertEqual(0, offset.seconds)
|
||||
|
||||
timestr = "20051213141205+0500"
|
||||
|
||||
time = ipautil.parse_generalized_time(timestr)
|
||||
self.assertEqual(5, time.tzinfo.houroffset)
|
||||
self.assertEqual(0, time.tzinfo.minoffset)
|
||||
offset = time.tzinfo.utcoffset(None)
|
||||
self.assertEqual(5 * 60 * 60, offset.seconds)
|
||||
|
||||
timestr = "20051213141205-0500"
|
||||
|
||||
time = ipautil.parse_generalized_time(timestr)
|
||||
self.assertEqual(-5, time.tzinfo.houroffset)
|
||||
self.assertEqual(0, time.tzinfo.minoffset)
|
||||
# NOTE - the offset is always positive - it's minutes
|
||||
# _east_ of UTC
|
||||
offset = time.tzinfo.utcoffset(None)
|
||||
self.assertEqual((24 - 5) * 60 * 60, offset.seconds)
|
||||
|
||||
timestr = "20051213141205-0930"
|
||||
|
||||
time = ipautil.parse_generalized_time(timestr)
|
||||
self.assertEqual(-9, time.tzinfo.houroffset)
|
||||
self.assertEqual(-30, time.tzinfo.minoffset)
|
||||
offset = time.tzinfo.utcoffset(None)
|
||||
self.assertEqual(((24 - 9) * 60 * 60) - (30 * 60), offset.seconds)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
@ -50,6 +50,7 @@ rm -rf %{buildroot}
|
||||
|
||||
%attr(755,root,root) %{plugin_dir}/libipa_pwd_extop.so
|
||||
%attr(755,root,root) %{plugin_dir}/libipa-memberof-plugin.so
|
||||
%attr(755,root,root) %{plugin_dir}/libipa-dna-plugin.so
|
||||
|
||||
|
||||
%changelog
|
||||
|
@ -50,6 +50,7 @@ rm -rf %{buildroot}
|
||||
|
||||
%attr(755,root,root) %{plugin_dir}/libipa_pwd_extop.so
|
||||
%attr(755,root,root) %{plugin_dir}/libipa-memberof-plugin.so
|
||||
%attr(755,root,root) %{plugin_dir}/libipa-dna-plugin.so
|
||||
|
||||
|
||||
%changelog
|
||||
|
@ -17,6 +17,7 @@ import ipa.ipaclient
|
||||
import ipa.user
|
||||
import xmlrpclib
|
||||
import forms.user
|
||||
from helpers import userhelper
|
||||
from ipa import ipaerror
|
||||
|
||||
ipa.config.init_config()
|
||||
@ -47,6 +48,14 @@ class Root(controllers.RootController):
|
||||
def index(self):
|
||||
return dict()
|
||||
|
||||
@expose()
|
||||
def topsearch(self, **kw):
|
||||
if kw.get('searchtype') == "Users":
|
||||
return self.userlist(uid=kw.get('searchvalue'))
|
||||
else:
|
||||
return self.index()
|
||||
|
||||
|
||||
|
||||
########
|
||||
# User #
|
||||
@ -107,7 +116,7 @@ class Root(controllers.RootController):
|
||||
def userupdate(self, **kw):
|
||||
"""Updates an existing user"""
|
||||
restrict_post()
|
||||
if kw.get('submit') == 'Cancel':
|
||||
if kw.get('submit') == 'Cancel Edit':
|
||||
turbogears.flash("Edit user cancelled")
|
||||
raise turbogears.redirect('/usershow', uid=kw.get('uid'))
|
||||
|
||||
@ -188,7 +197,7 @@ class Root(controllers.RootController):
|
||||
def userindex(self):
|
||||
raise turbogears.redirect("/userlist")
|
||||
|
||||
@expose()
|
||||
# @expose()
|
||||
def generate_password(self):
|
||||
password = ""
|
||||
generator = random.SystemRandom()
|
||||
@ -203,6 +212,9 @@ class Root(controllers.RootController):
|
||||
if (len(givenname) == 0) or (len(sn) == 0):
|
||||
return ""
|
||||
|
||||
givenname = givenname.lower()
|
||||
sn = sn.lower()
|
||||
|
||||
uid = givenname[0] + sn[:7]
|
||||
try:
|
||||
client.get_user_by_uid(uid)
|
||||
@ -244,6 +256,9 @@ class Root(controllers.RootController):
|
||||
if (len(givenname) == 0) or (len(sn) == 0):
|
||||
return ""
|
||||
|
||||
givenname = givenname.lower()
|
||||
sn = sn.lower()
|
||||
|
||||
# TODO - get from config
|
||||
domain = "freeipa.org"
|
||||
|
||||
|
@ -3,29 +3,41 @@ from turbogears import validators, widgets
|
||||
|
||||
class UserFields():
|
||||
uid = widgets.TextField(name="uid", label="Login")
|
||||
userpassword = widgets.TextField(name="userpassword", label="Password")
|
||||
userpassword = widgets.PasswordField(name="userpassword", label="Password")
|
||||
userpassword_confirm = widgets.PasswordField(name="userpassword_confirm",
|
||||
label="Confirm Password")
|
||||
uidnumber = widgets.TextField(name="uidnumber", label="UID")
|
||||
gidnumber = widgets.TextField(name="gidnumber", label="GID")
|
||||
givenname = widgets.TextField(name="givenname", label="First name")
|
||||
sn = widgets.TextField(name="sn", label="Last name")
|
||||
mail = widgets.TextField(name="mail", label="E-mail address")
|
||||
telephonenumber = widgets.TextField(name="telephonenumber", label="Phone")
|
||||
nsAccountLock = widgets.CheckBox(name="nsAccountLock", label="Account Deactivated")
|
||||
|
||||
uid.validator = validators.PlainText(not_empty=True)
|
||||
userpassword.validator = validators.String(not_empty=True)
|
||||
givenname.validator = validators.String(not_empty=True)
|
||||
sn.validator = validators.String(not_empty=True)
|
||||
mail.validator = validators.Email(not_empty=True)
|
||||
# validators.PhoneNumber may be a bit too picky, requiring an area code
|
||||
telephonenumber.validator = validators.PlainText(not_empty=True)
|
||||
# nsAccountLock = widgets.CheckBox(name="nsAccountLock", label="Account Deactivated")
|
||||
nsAccountLock = widgets.SingleSelectField(name="nsAccountLock",
|
||||
label="Account Status",
|
||||
options = [("", "active"), ("true", "inactive")])
|
||||
|
||||
uid_hidden = widgets.HiddenField(name="uid")
|
||||
uidnumber_hidden = widgets.HiddenField(name="uidnumber")
|
||||
gidnumber_hidden = widgets.HiddenField(name="gidnumber")
|
||||
krbPasswordExpiration_hidden = widgets.HiddenField(name="krbPasswordExpiration")
|
||||
|
||||
user_orig = widgets.HiddenField(name="user_orig")
|
||||
|
||||
class UserNewValidator(validators.Schema):
|
||||
uid = validators.PlainText(not_empty=True)
|
||||
userpassword = validators.String(not_empty=False)
|
||||
userpassword_confirm = validators.String(not_empty=False)
|
||||
givenname = validators.String(not_empty=True)
|
||||
sn = validators.String(not_empty=True)
|
||||
mail = validators.Email(not_empty=True)
|
||||
# validators.PhoneNumber may be a bit too picky, requiring an area code
|
||||
# telephonenumber = validators.PlainText(not_empty=False)
|
||||
|
||||
chained_validators = [
|
||||
validators.FieldsMatch('userpassword', 'userpassword_confirm')
|
||||
]
|
||||
|
||||
|
||||
class UserNewForm(widgets.Form):
|
||||
params = ['user']
|
||||
@ -34,6 +46,8 @@ class UserNewForm(widgets.Form):
|
||||
UserFields.uidnumber, UserFields.gidnumber,
|
||||
UserFields.sn, UserFields.mail]
|
||||
|
||||
validator = UserNewValidator()
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(UserNewForm,self).__init__(*args, **kw)
|
||||
(self.template_c, self.template) = widgets.meta.load_kid_template("ipagui.templates.usernewform")
|
||||
@ -46,6 +60,18 @@ class UserNewForm(widgets.Form):
|
||||
def has_foo(self):
|
||||
return False
|
||||
|
||||
class UserEditValidator(validators.Schema):
|
||||
userpassword = validators.String(not_empty=False)
|
||||
userpassword_confirm = validators.String(not_empty=False)
|
||||
givenname = validators.String(not_empty=True)
|
||||
sn = validators.String(not_empty=True)
|
||||
mail = validators.Email(not_empty=True)
|
||||
# validators.PhoneNumber may be a bit too picky, requiring an area code
|
||||
# telephonenumber = validators.PlainText(not_empty=False)
|
||||
|
||||
chained_validators = [
|
||||
validators.FieldsMatch('userpassword', 'userpassword_confirm')
|
||||
]
|
||||
|
||||
class UserEditForm(widgets.Form):
|
||||
params = ['user']
|
||||
@ -53,8 +79,11 @@ class UserEditForm(widgets.Form):
|
||||
fields = [UserFields.givenname, UserFields.sn, UserFields.mail,
|
||||
UserFields.uid_hidden, UserFields.user_orig,
|
||||
UserFields.uidnumber_hidden, UserFields.gidnumber_hidden,
|
||||
UserFields.krbPasswordExpiration_hidden,
|
||||
]
|
||||
|
||||
validator = UserEditValidator()
|
||||
|
||||
def __init__(self, *args, **kw):
|
||||
super(UserEditForm,self).__init__(*args, **kw)
|
||||
(self.template_c, self.template) = widgets.meta.load_kid_template("ipagui.templates.usereditform")
|
||||
|
1
ipa-server/ipa-gui/ipagui/helpers/__init__.py
Normal file
1
ipa-server/ipa-gui/ipagui/helpers/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# __init__.py
|
29
ipa-server/ipa-gui/ipagui/helpers/userhelper.py
Normal file
29
ipa-server/ipa-gui/ipagui/helpers/userhelper.py
Normal file
@ -0,0 +1,29 @@
|
||||
import sys
|
||||
import datetime
|
||||
|
||||
from ipa import ipautil
|
||||
|
||||
def password_expires_in(datestr):
|
||||
"""Returns the number of days that password expires in. Returns a negative number
|
||||
if the password is already expired."""
|
||||
if (datestr == None) or (datestr == ""):
|
||||
return sys.maxint
|
||||
|
||||
expdate = ipautil.parse_generalized_time(datestr)
|
||||
if not expdate:
|
||||
return sys.maxint
|
||||
|
||||
delta = expdate - datetime.datetime.now()
|
||||
return delta.days
|
||||
|
||||
def password_is_expired(days):
|
||||
return days < 0
|
||||
|
||||
def password_expires_soon(days):
|
||||
return (not password_is_expired(days)) and (days < 7)
|
||||
|
||||
def account_status_display(status):
|
||||
if status == "true":
|
||||
return "inactive"
|
||||
else:
|
||||
return "active"
|
@ -17,6 +17,7 @@ body {
|
||||
background:#ccc; /* should be same as #sidebar */
|
||||
margin:0 auto;
|
||||
width:100%;
|
||||
clear:both;
|
||||
}
|
||||
|
||||
|
||||
@ -24,9 +25,26 @@ body {
|
||||
background:#fff;
|
||||
}
|
||||
|
||||
#header h1 {
|
||||
padding:5px;
|
||||
margin:0;
|
||||
#header #logo {
|
||||
float:left;
|
||||
}
|
||||
|
||||
#header #headerinfo {
|
||||
text-align:right;
|
||||
padding-right:10px;
|
||||
}
|
||||
|
||||
#header #headerinfo #login {
|
||||
}
|
||||
|
||||
#header #headerinfo #topsearch {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.searchtext {
|
||||
background-color:#E5F1F4;
|
||||
border:1px solid #8E8E8E;
|
||||
color:#444444;
|
||||
}
|
||||
|
||||
|
||||
@ -75,7 +93,11 @@ body {
|
||||
float:left;
|
||||
width:10%;
|
||||
padding: 5px;
|
||||
font-size: small;
|
||||
font-size: medium;
|
||||
}
|
||||
|
||||
#sidebar p {
|
||||
line-height: 150%;
|
||||
}
|
||||
|
||||
#sidebar h2 {
|
||||
@ -140,6 +162,12 @@ body {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.warning_message {
|
||||
font-size: 120%;
|
||||
color: #ee0000;
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
.fielderror {
|
||||
color: red;
|
||||
font-weight: bold;
|
||||
|
@ -24,35 +24,66 @@
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div id="page">
|
||||
<div id="header">
|
||||
<div id="header">
|
||||
<div id="logo">
|
||||
<a href="${tg.url('/')}"><img
|
||||
src="${tg.url('/static/images/logo.png')}"
|
||||
border="0"
|
||||
/></a>
|
||||
src="${tg.url('/static/images/logo.png')}"
|
||||
border="0"
|
||||
/></a>
|
||||
</div>
|
||||
<div id="headerinfo">
|
||||
<div id="login">
|
||||
Logged in as: ace
|
||||
</div>
|
||||
<div id="topsearch">
|
||||
<form action="${tg.url('/topsearch')}" method="post">
|
||||
<select name="searchtype">
|
||||
<option>Users</option>
|
||||
<option>Groups</option>
|
||||
</select>
|
||||
<input class="searchtext" id="topsearchbox" type="text"
|
||||
name="searchvalue"
|
||||
value="Type search terms here."
|
||||
onfocus="clearsearch()" />
|
||||
<input type="submit" value="Search"/>
|
||||
</form>
|
||||
<script type="text/javascript">
|
||||
function clearsearch() {
|
||||
topsearchbox = document.getElementById('topsearchbox');
|
||||
topsearchbox.onfocus = null;
|
||||
topsearchbox.value = "";
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="page">
|
||||
<div id="nav"><!--
|
||||
This used to have links. Keeping around in case we move them back...
|
||||
--></div>
|
||||
|
||||
<div id="sidebar">
|
||||
<h2>Tasks</h2>
|
||||
<p>
|
||||
<a href="${tg.url('/usernew')}">Add Person</a><br/>
|
||||
<a href="${tg.url('/userlist')}">Find People</a><br/>
|
||||
<br />
|
||||
</p>
|
||||
<p>
|
||||
<a href="${tg.url('/groupindex')}">Add Group</a><br/>
|
||||
<a href="${tg.url('/groupindex')}">Find Groups</a><br/>
|
||||
<br />
|
||||
</p>
|
||||
<p>
|
||||
<a href="${tg.url('/')}">Manage Policy</a><br/>
|
||||
<a href="${tg.url('/')}">Self Service</a><br/>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div py:replace="[item.text]+item[:]"></div>
|
||||
|
||||
|
||||
<div id="footer">
|
||||
<a href="http://www.freeipa.com/">Powered by FreeIPA</a>
|
||||
<a href="http://www.freeipa.com/" target="_blank">Powered by FreeIPA</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -8,6 +8,24 @@
|
||||
<body>
|
||||
<h2>Edit Person</h2>
|
||||
|
||||
<?python
|
||||
from ipagui.helpers import userhelper
|
||||
pw_expires_days = userhelper.password_expires_in(user.get("krbPasswordExpiration"))
|
||||
pw_expires_soon = userhelper.password_expires_soon(pw_expires_days)
|
||||
pw_is_expired = userhelper.password_is_expired(pw_expires_days)
|
||||
if pw_expires_days != 1:
|
||||
days_suffix = "s"
|
||||
else:
|
||||
days_suffix = ""
|
||||
?>
|
||||
|
||||
<div py:if='pw_expires_soon' class="warning_message">
|
||||
Password will expire in ${pw_expires_days} day${days_suffix}
|
||||
</div>
|
||||
<div py:if='pw_is_expired' class="warning_message">
|
||||
Password has expired
|
||||
</div>
|
||||
|
||||
${form.display(action="userupdate", value=user)}
|
||||
</body>
|
||||
</html>
|
||||
|
@ -56,8 +56,9 @@
|
||||
<span py:replace="user.userpassword.display(value_for(user.userpassword))" />
|
||||
<span py:if="tg.errors.get('userpassword')" class="fielderror"
|
||||
py:content="tg.errors.get('userpassword')" />
|
||||
<span id="password_text">********</span>
|
||||
|
||||
<!--
|
||||
<span id="password_text">********</span>
|
||||
<input id="genpassword_button" type="button" value="Generate Password"
|
||||
disabled="true"
|
||||
onclick="new Ajax.Request('${tg.url('/generate_password')}',
|
||||
@ -89,6 +90,20 @@
|
||||
}
|
||||
}
|
||||
</script>
|
||||
-->
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th valign="top">
|
||||
<label class="fieldlabel" for="${user.userpassword_confirm.field_id}"
|
||||
py:content="user.userpassword_confirm.label" />:
|
||||
</th>
|
||||
<td valign="top">
|
||||
<span py:replace="user.userpassword_confirm.display(
|
||||
value_for(user.userpassword_confirm))" />
|
||||
<span py:if="tg.errors.get('userpassword_confirm')" class="fielderror"
|
||||
py:content="tg.errors.get('userpassword_confirm')" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -158,11 +173,13 @@
|
||||
<tr>
|
||||
<th>
|
||||
<br />
|
||||
<input type="submit" class="submitbutton" name="submit" value="Submit"/>
|
||||
<input type="submit" class="submitbutton" name="submit"
|
||||
value="Update Person"/>
|
||||
</th>
|
||||
<td>
|
||||
<br />
|
||||
<input type="submit" class="submitbutton" name="submit" value="Cancel" />
|
||||
<input type="submit" class="submitbutton" name="submit"
|
||||
value="Cancel Edit" />
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
@ -6,20 +6,18 @@
|
||||
<title>Find People</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Find People</h2>
|
||||
<div id="search">
|
||||
<form action="${tg.url('/userlist')}" method="post">
|
||||
Search:
|
||||
<input id="uid" type="text" name="uid" value="${uid}" />
|
||||
<input type="submit" />
|
||||
<input type="submit" value="Find People"/>
|
||||
</form>
|
||||
<script type="text/javascript">
|
||||
document.getElementById("uid").focus();
|
||||
</script>
|
||||
</div>
|
||||
<div py:if='users != None'>
|
||||
<div py:if='(users != None) and (len(users) > 0)'>
|
||||
<h2>${len(users)} results returned:</h2>
|
||||
<table id="resultstable" py:if='len(users) > 0'>
|
||||
<table id="resultstable">
|
||||
<tr>
|
||||
<th>
|
||||
<label class="fieldlabel" py:content="fields.uid.label" />
|
||||
@ -61,9 +59,9 @@
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div py:if='len(users) == 0'>
|
||||
No results found.
|
||||
</div>
|
||||
</div>
|
||||
<div py:if='(users != None) and (len(users) == 0)'>
|
||||
<h2>No results found for "${uid}"</h2>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -27,6 +27,9 @@
|
||||
<span py:if="tg.errors.get('sn')" class="fielderror"
|
||||
py:content="tg.errors.get('sn')" />
|
||||
<script type="text/javascript">
|
||||
var uid_suggest = ""
|
||||
var mail_suggest = ""
|
||||
|
||||
function autofill(self) {
|
||||
givenname = document.getElementById('form_givenname');
|
||||
sn = document.getElementById('form_sn');
|
||||
@ -35,22 +38,24 @@
|
||||
}
|
||||
uid = document.getElementById('form_uid');
|
||||
mail = document.getElementById('form_mail');
|
||||
if (uid.value == "") {
|
||||
if ((uid.value == "") || (uid.value == uid_suggest)) {
|
||||
new Ajax.Request('${tg.url('/suggest_uid')}', {
|
||||
method: 'get',
|
||||
parameters: {'givenname': givenname.value, 'sn': sn.value},
|
||||
onSuccess: function(transport) {
|
||||
uid.value = transport.responseText;
|
||||
uid_suggest = uid.value;
|
||||
new Effect.Highlight(uid);
|
||||
}
|
||||
});
|
||||
}
|
||||
if (mail.value == "") {
|
||||
if ((mail.value == "") || (mail.value == mail_suggest)) {
|
||||
new Ajax.Request('${tg.url('/suggest_email')}', {
|
||||
method: 'get',
|
||||
parameters: {'givenname': givenname.value, 'sn': sn.value},
|
||||
onSuccess: function(transport) {
|
||||
mail.value = transport.responseText;
|
||||
mail_suggest = mail.value;
|
||||
new Effect.Highlight(mail);
|
||||
}
|
||||
});
|
||||
@ -87,6 +92,7 @@
|
||||
<span py:if="tg.errors.get('userpassword')" class="fielderror"
|
||||
py:content="tg.errors.get('userpassword')" />
|
||||
|
||||
<!--
|
||||
<input type="button" value="Generate Password"
|
||||
onclick="new Ajax.Request('${tg.url('/generate_password')}',
|
||||
{
|
||||
@ -96,6 +102,20 @@
|
||||
transport.responseText;
|
||||
}
|
||||
});" />
|
||||
-->
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>
|
||||
<label class="fieldlabel" for="${user.userpassword_confirm.field_id}"
|
||||
py:content="user.userpassword_confirm.label" />:
|
||||
</th>
|
||||
<td>
|
||||
<span py:replace="user.userpassword_confirm.display(
|
||||
value_for(user.userpassword_confirm))" />
|
||||
<span py:if="tg.errors.get('userpassword_confirm')" class="fielderror"
|
||||
py:content="tg.errors.get('userpassword_confirm')" />
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
@ -167,15 +187,11 @@
|
||||
|
||||
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
|
||||
<tr>
|
||||
<th>
|
||||
<br />
|
||||
<input type="submit" class="submitbutton" name="submit" value="Submit"/>
|
||||
</th>
|
||||
<th></th>
|
||||
<td>
|
||||
<br />
|
||||
<input type="submit" class="submitbutton" name="submit" value="Cancel" />
|
||||
<input type="submit" class="submitbutton" name="submit" value="Add Person"/>
|
||||
</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
@ -8,6 +8,24 @@
|
||||
<body>
|
||||
<h2>View Person</h2>
|
||||
|
||||
<?python
|
||||
from ipagui.helpers import userhelper
|
||||
pw_expires_days = userhelper.password_expires_in(user.get("krbPasswordExpiration"))
|
||||
pw_expires_soon = userhelper.password_expires_soon(pw_expires_days)
|
||||
pw_is_expired = userhelper.password_is_expired(pw_expires_days)
|
||||
if pw_expires_days != 1:
|
||||
days_suffix = "s"
|
||||
else:
|
||||
days_suffix = ""
|
||||
?>
|
||||
|
||||
<div py:if='pw_expires_soon' class="warning_message">
|
||||
Password will expire in ${pw_expires_days} day${days_suffix}
|
||||
</div>
|
||||
<div py:if='pw_is_expired' class="warning_message">
|
||||
Password has expired
|
||||
</div>
|
||||
|
||||
<div class="formsection">Identity Details</div>
|
||||
<table class="formtable" cellpadding="2" cellspacing="0" border="0">
|
||||
<tr>
|
||||
@ -68,7 +86,7 @@
|
||||
<th>
|
||||
<label class="fieldlabel" py:content="fields.nsAccountLock.label" />:
|
||||
</th>
|
||||
<td>${user.get("nsAccountLock")}</td>
|
||||
<td>${userhelper.account_status_display(user.get("nsAccountLock"))}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
|
@ -2,7 +2,8 @@
|
||||
Required packages:
|
||||
|
||||
krb5-server
|
||||
fedora-ds-base / fedora-ds-base-devel
|
||||
fedora-ds-base
|
||||
fedora-ds-base-devel
|
||||
openldap-clients
|
||||
krb5-server-ldap
|
||||
cyrus-sasl-gssapi
|
||||
@ -13,12 +14,40 @@ openssl-devel
|
||||
|
||||
Installation example:
|
||||
|
||||
TEMPORARY: (until fedora ds scripts are fixed)
|
||||
please use the fedora-ds.init.patch under share/ to patch your init scripts before
|
||||
running ipa-server-install
|
||||
TEMPORARY: until bug https://bugzilla.redhat.com/show_bug.cgi?id=248169 is
|
||||
fixed.
|
||||
|
||||
cd ipa-install
|
||||
make install
|
||||
cd ..
|
||||
/usr/sbin/ipa-server-install -u fds -r FREEIPA.ORG -p freeipa -m ipafree
|
||||
Please apply the fedora-ds.init.patch in freeipa/ipa-server/ipa-install/share/
|
||||
to patch your init scripts before running ipa-server-install. This tells
|
||||
FDS where to find its kerberos keytab.
|
||||
|
||||
Things done as root are denoted by #. Things done as a unix user are denoted
|
||||
by %.
|
||||
|
||||
# cd freeipa
|
||||
# patch -p0 < ipa-server/ipa-install/share/fedora-ds.init.patch
|
||||
|
||||
Now to do the installation.
|
||||
|
||||
# cd freeipa
|
||||
# make install
|
||||
# /usr/sbin/ipa-server-install -u fds -r FREEIPA.ORG -p freeipa -P ipafree
|
||||
|
||||
For more verbose output add the -d flag
|
||||
|
||||
You have a basic working system with one super administrator (named admin).
|
||||
|
||||
To create another administrative user:
|
||||
|
||||
% kinit admin@FREEIPA.ORG
|
||||
% /usr/sbin/ipa-adduser -f Test -l User test
|
||||
% ldappasswd -Y GSSAPI -h localhost -s password uid=test,cn=users,cn=accounts,dc=freeipa,dc=org
|
||||
% /usr/sbin/ipa-groupmod -a test admins
|
||||
|
||||
An admin user is just a regular user in the group admin.
|
||||
|
||||
Now you can destroy the old ticket and log in as test:
|
||||
|
||||
% kdestroy
|
||||
% kinit test@FREEIPA.ORG
|
||||
% /usr/sbin/ipa-finduser test
|
||||
|
@ -1,4 +1,4 @@
|
||||
SUBDIRS=ipa-pwd-extop ipa-memberof
|
||||
SUBDIRS=ipa-pwd-extop ipa-memberof dna
|
||||
|
||||
all:
|
||||
@for subdir in $(SUBDIRS); do \
|
||||
|
34
ipa-server/ipa-slapi-plugins/dna/Makefile
Normal file
34
ipa-server/ipa-slapi-plugins/dna/Makefile
Normal file
@ -0,0 +1,34 @@
|
||||
DIRSRV ?= dirsrv
|
||||
PREFIX ?= $(DESTDIR)/usr
|
||||
LIBDIR ?= $(PREFIX)/lib/$(DIRSRV)/plugins
|
||||
LIB64DIR ?= $(PREFIX)/lib64/$(DIRSRV)/plugins
|
||||
SHAREDIR = $(DESTDIR)/usr/share/ipa
|
||||
|
||||
SONAME = libipa-dna-plugin.so
|
||||
LDFLAGS += -llber
|
||||
CFLAGS ?= -g -Wall -Wshadow
|
||||
CFLAGS += -I/usr/include/$(DIRSRV) -I/usr/include/nss3 -I/usr/include/mozldap -I/usr/include/nspr4 -fPIC -DPIC
|
||||
|
||||
OBJS = $(patsubst %.c,%.o,$(wildcard *.c))
|
||||
|
||||
all: $(OBJS)
|
||||
$(CC) $(LDFLAGS) $(OBJS) -Wl,-soname -Wl,$(SONAME) -shared -o $(SONAME)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
install:
|
||||
-mkdir -p $(LIBDIR)
|
||||
if [ -e $(PREFIX)/lib/$(DIRSRV) ]; then \
|
||||
install -m 644 $(SONAME) $(LIBDIR); \
|
||||
else \
|
||||
install -m 644 $(SONAME) $(LIB64DIR); \
|
||||
fi
|
||||
install -m 644 *.ldif $(SHAREDIR)
|
||||
|
||||
clean:
|
||||
rm -f *.o
|
||||
rm -f $(SONAME)
|
||||
rm -f *~
|
||||
|
||||
|
13
ipa-server/ipa-slapi-plugins/dna/dna-conf.ldif
Normal file
13
ipa-server/ipa-slapi-plugins/dna/dna-conf.ldif
Normal file
@ -0,0 +1,13 @@
|
||||
dn: cn=ipa-dna,cn=plugins,cn=config
|
||||
objectclass: top
|
||||
objectclass: nsSlapdPlugin
|
||||
objectclass: extensibleObject
|
||||
cn: ipa-dna
|
||||
nsslapd-pluginpath: libipa-dna-plugin
|
||||
nsslapd-plugininitfunc: dna_init
|
||||
nsslapd-plugintype: postoperation
|
||||
nsslapd-pluginenabled: on
|
||||
nsslapd-pluginid: ipa-dna
|
||||
nsslapd-pluginversion: 1.0
|
||||
nsslapd-pluginvendor: Red Hat
|
||||
nsslapd-plugindescription: Distributed numeric assignment plugin
|
1174
ipa-server/ipa-slapi-plugins/dna/dna.c
Normal file
1174
ipa-server/ipa-slapi-plugins/dna/dna.c
Normal file
File diff suppressed because it is too large
Load Diff
@ -24,7 +24,7 @@ import tempfile
|
||||
import shutil
|
||||
import logging
|
||||
import pwd
|
||||
from util import *
|
||||
from ipa.ipautil import *
|
||||
|
||||
|
||||
SHARE_DIR = "/usr/share/ipa/"
|
||||
|
@ -35,13 +35,14 @@ import cStringIO
|
||||
import time
|
||||
import operator
|
||||
import struct
|
||||
import ldap.sasl
|
||||
from ldap.controls import LDAPControl,DecodeControlTuples,EncodeControlTuples
|
||||
from ldap.modlist import modifyModlist
|
||||
|
||||
from ldap.ldapobject import SimpleLDAPObject
|
||||
|
||||
from ipa import ipaerror, ipautil
|
||||
|
||||
# Global variable to define SASL auth
|
||||
sasl_auth = ldap.sasl.sasl({},'GSSAPI')
|
||||
|
||||
class Entry:
|
||||
"""This class represents an LDAP Entry object. An LDAP entry consists of a DN
|
||||
and a list of attributes. Each attribute consists of a name and a list of
|
||||
@ -196,22 +197,34 @@ class IPAdmin(SimpleLDAPObject):
|
||||
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
|
||||
|
||||
def __localinit__(self):
|
||||
SimpleLDAPObject.__init__(self,'ldaps://%s:%d' % (self.host,self.port))
|
||||
"""If a CA certificate is provided then it is assumed that we are
|
||||
doing SSL client authentication with proxy auth.
|
||||
|
||||
If a CA certificate is not present then it is assumed that we are
|
||||
using a forwarded kerberos ticket for SASL auth. SASL provides
|
||||
its own encryption.
|
||||
"""
|
||||
if self.cacert is not None:
|
||||
SimpleLDAPObject.__init__(self,'ldaps://%s:%d' % (self.host,self.port))
|
||||
else:
|
||||
SimpleLDAPObject.__init__(self,'ldap://%s:%d' % (self.host,self.port))
|
||||
|
||||
def __init__(self,host,port,cacert,bindcert,bindkey,proxydn=None):
|
||||
"""We just set our instance variables and wrap the methods - the real work is
|
||||
done in __localinit__ and __initPart2 - these are separated out this way so
|
||||
that we can call them from places other than instance creation e.g. when
|
||||
using the start command, we just need to reconnect, not create a new instance"""
|
||||
"""We just set our instance variables and wrap the methods - the real
|
||||
work is done in __localinit__ and __initPart2 - these are separated
|
||||
out this way so that we can call them from places other than
|
||||
instance creation e.g. when we just need to reconnect, not create a
|
||||
new instance"""
|
||||
# ldap.set_option(ldap.OPT_DEBUG_LEVEL,255)
|
||||
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,cacert)
|
||||
ldap.set_option(ldap.OPT_X_TLS_CERTFILE,bindcert)
|
||||
ldap.set_option(ldap.OPT_X_TLS_KEYFILE,bindkey)
|
||||
if cacert is not None:
|
||||
ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,cacert)
|
||||
ldap.set_option(ldap.OPT_X_TLS_CERTFILE,bindcert)
|
||||
ldap.set_option(ldap.OPT_X_TLS_KEYFILE,bindkey)
|
||||
|
||||
self.__wrapmethods()
|
||||
self.port = port or 389
|
||||
self.sslport = 0
|
||||
self.host = host
|
||||
self.cacert = cacert
|
||||
self.bindcert = bindcert
|
||||
self.bindkey = bindkey
|
||||
self.proxydn = proxydn
|
||||
@ -251,6 +264,12 @@ class IPAdmin(SimpleLDAPObject):
|
||||
def set_proxydn(self, proxydn):
|
||||
self.proxydn = proxydn
|
||||
|
||||
def set_keytab(self, keytab):
|
||||
if keytab is not None:
|
||||
os.environ["KRB5CCNAME"] = keytab
|
||||
self.sasl_interactive_bind_s("", sasl_auth)
|
||||
self.proxydn = None
|
||||
|
||||
def getEntry(self,*args):
|
||||
"""This wraps the search function. It is common to just get one entry"""
|
||||
|
||||
@ -346,7 +365,8 @@ class IPAdmin(SimpleLDAPObject):
|
||||
sctrl = self.__get_server_controls__()
|
||||
|
||||
try:
|
||||
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
|
||||
if sctrl is not None:
|
||||
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
|
||||
self.add_s(*args)
|
||||
except ldap.ALREADY_EXISTS:
|
||||
raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE)
|
||||
@ -366,7 +386,8 @@ class IPAdmin(SimpleLDAPObject):
|
||||
raise ipaerror.gen_exception(ipaerror.LDAP_EMPTY_MODLIST)
|
||||
|
||||
try:
|
||||
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
|
||||
if sctrl is not None:
|
||||
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
|
||||
self.modify_s(dn, modlist)
|
||||
# this is raised when a 'delete' attribute isn't found.
|
||||
# it indicates the previous attribute was removed by another
|
||||
@ -428,7 +449,8 @@ class IPAdmin(SimpleLDAPObject):
|
||||
modlist.append((operation, "nsAccountlock", "true"))
|
||||
|
||||
try:
|
||||
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
|
||||
if sctrl is not None:
|
||||
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
|
||||
self.modify_s(dn, modlist)
|
||||
except ldap.LDAPError, e:
|
||||
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
|
||||
@ -440,7 +462,8 @@ class IPAdmin(SimpleLDAPObject):
|
||||
sctrl = self.__get_server_controls__()
|
||||
|
||||
try:
|
||||
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
|
||||
if sctrl is not None:
|
||||
self.set_option(ldap.OPT_SERVER_CONTROLS, sctrl)
|
||||
self.delete_s(*args)
|
||||
except ldap.LDAPError, e:
|
||||
raise ipaerror.gen_exception(ipaerror.LDAP_DATABASE_ERROR, None, e)
|
||||
|
@ -29,7 +29,7 @@ import os
|
||||
import pwd
|
||||
import socket
|
||||
import time
|
||||
from util import *
|
||||
from ipa.ipautil import *
|
||||
|
||||
def host_to_domain(fqdn):
|
||||
s = fqdn.split(".")
|
||||
|
@ -47,20 +47,31 @@ DefaultGroupContainer = "cn=groups,cn=accounts"
|
||||
# this is not anticipated.
|
||||
class IPAConnPool:
|
||||
def __init__(self):
|
||||
self.numentries = 0
|
||||
self.freelist = []
|
||||
|
||||
def getConn(self, host, port, bindca, bindcert, bindkey, proxydn=None):
|
||||
self.numentries = self.numentries + 1
|
||||
def getConn(self, host, port, bindca, bindcert, bindkey, proxydn=None, keytab=None):
|
||||
conn = None
|
||||
if len(self.freelist) > 0:
|
||||
conn = self.freelist.pop()
|
||||
else:
|
||||
for i in range(len(self.freelist)):
|
||||
c = self.freelist[i]
|
||||
if ((c.host == host) and (c.port == port)):
|
||||
conn = self.freelist.pop(i)
|
||||
break
|
||||
if conn is None:
|
||||
conn = ipaserver.ipaldap.IPAdmin(host,port,bindca,bindcert,bindkey)
|
||||
conn.set_proxydn(proxydn)
|
||||
if proxydn is not None:
|
||||
conn.set_proxydn(proxydn)
|
||||
else:
|
||||
conn.set_keytab(keytab)
|
||||
return conn
|
||||
|
||||
def releaseConn(self, conn):
|
||||
self.freelist.append(conn)
|
||||
# We can't re-use SASL connections. If proxydn is None it means
|
||||
# we have a keytab set. See ipaldap.set_keytab
|
||||
if conn.proxydn is None:
|
||||
conn.unbind_s()
|
||||
else:
|
||||
self.freelist.append(conn)
|
||||
|
||||
class IPAServer:
|
||||
|
||||
@ -68,7 +79,8 @@ class IPAServer:
|
||||
global _LDAPPool
|
||||
# FIXME, this needs to be auto-discovered
|
||||
self.host = 'localhost'
|
||||
self.port = 636
|
||||
self.port = 389
|
||||
self.sslport = 636
|
||||
self.bindcert = "/usr/share/ipa/cert.pem"
|
||||
self.bindkey = "/usr/share/ipa/key.pem"
|
||||
self.bindca = "/usr/share/ipa/cacert.asc"
|
||||
@ -79,24 +91,84 @@ class IPAServer:
|
||||
self.basedn = ipa.ipautil.realm_to_suffix(ipa.config.config.get_realm())
|
||||
self.scope = ldap.SCOPE_SUBTREE
|
||||
self.princ = None
|
||||
self.keytab = None
|
||||
|
||||
def set_principal(self, princ):
|
||||
self.princ = princ
|
||||
|
||||
def set_keytab(self, keytab):
|
||||
self.keytab = keytab
|
||||
|
||||
def get_dn_from_principal(self, princ):
|
||||
"""Given a kerberls principal get the LDAP uid"""
|
||||
"""Given a kerberos principal get the LDAP uid"""
|
||||
global _LDAPPool
|
||||
|
||||
filter = "(krbPrincipalName=" + princ + ")"
|
||||
# The only anonymous search we should have
|
||||
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,None)
|
||||
conn = _LDAPPool.getConn(self.host,self.sslport,self.bindca,self.bindcert,self.bindkey,None,None)
|
||||
try:
|
||||
ent = m1.getEntry(self.basedn, self.scope, filter, ['dn'])
|
||||
ent = conn.getEntry(self.basedn, self.scope, filter, ['dn'])
|
||||
finally:
|
||||
_LDAPPool.releaseConn(m1)
|
||||
_LDAPPool.releaseConn(conn)
|
||||
|
||||
return "dn:" + ent.dn
|
||||
|
||||
def __setup_connection(self, opts):
|
||||
"""Set up common things done in the connection.
|
||||
If there is a keytab then return None as the proxy dn and the keytab
|
||||
otherwise return the proxy dn and None as the keytab.
|
||||
|
||||
We only want one or the other used at one time and we prefer
|
||||
the keytab. So if there is a keytab, return that and None for
|
||||
proxy dn to make calling getConn() easier.
|
||||
"""
|
||||
|
||||
if opts:
|
||||
if opts.get('keytab'):
|
||||
self.set_keytab(opts['keytab'])
|
||||
self.set_principal(None)
|
||||
else:
|
||||
self.set_keytab(None)
|
||||
self.set_principal(opts['remoteuser'])
|
||||
else:
|
||||
self.set_keytab(None)
|
||||
# The caller should have already set the principal
|
||||
|
||||
if self.princ is not None:
|
||||
return self.get_dn_from_principal(self.princ), None
|
||||
else:
|
||||
return None, self.keytab
|
||||
|
||||
def getConnection(self, opts):
|
||||
"""Wrapper around IPAConnPool.getConn() so we don't have to pass
|
||||
around self.* every time a connection is needed.
|
||||
|
||||
For SASL connections (where we have a keytab) we can't set
|
||||
the SSL variables for certificates. It confuses the ldap
|
||||
module.
|
||||
"""
|
||||
global _LDAPPool
|
||||
|
||||
(proxy_dn, keytab) = self.__setup_connection(opts)
|
||||
|
||||
if keytab is not None:
|
||||
bindca = None
|
||||
bindcert = None
|
||||
bindkey = None
|
||||
port = self.port
|
||||
else:
|
||||
bindca = self.bindca
|
||||
bindcert = self.bindcert
|
||||
bindkey = self.bindkey
|
||||
port = self.sslport
|
||||
|
||||
return _LDAPPool.getConn(self.host,port,bindca,bindcert,bindkey,proxy_dn,keytab)
|
||||
|
||||
def releaseConnection(self, conn):
|
||||
global _LDAPPool
|
||||
|
||||
_LDAPPool.releaseConn(conn)
|
||||
|
||||
def convert_entry(self, ent):
|
||||
entry = dict(ent.data)
|
||||
entry['dn'] = ent.dn
|
||||
@ -110,24 +182,17 @@ class IPAServer:
|
||||
entry[key] = value[0]
|
||||
return entry
|
||||
|
||||
|
||||
def __get_entry (self, base, filter, sattrs=None, opts=None):
|
||||
"""Get a specific entry. Return as a dict of values.
|
||||
Multi-valued fields are represented as lists.
|
||||
"""
|
||||
global _LDAPPool
|
||||
ent=""
|
||||
|
||||
if opts:
|
||||
self.set_principal(opts['remoteuser'])
|
||||
|
||||
dn = self.get_dn_from_principal(self.princ)
|
||||
|
||||
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
|
||||
conn = self.getConnection(opts)
|
||||
try:
|
||||
ent = m1.getEntry(base, self.scope, filter, sattrs)
|
||||
ent = conn.getEntry(base, self.scope, filter, sattrs)
|
||||
finally:
|
||||
_LDAPPool.releaseConn(m1)
|
||||
self.releaseConnection(conn)
|
||||
|
||||
return self.convert_entry(ent)
|
||||
|
||||
@ -137,8 +202,6 @@ class IPAServer:
|
||||
oldentry is a dict
|
||||
newentry is a dict
|
||||
"""
|
||||
global _LDAPPool
|
||||
|
||||
oldentry = self.convert_scalar_values(oldentry)
|
||||
newentry = self.convert_scalar_values(newentry)
|
||||
|
||||
@ -150,16 +213,11 @@ class IPAServer:
|
||||
except KeyError, e:
|
||||
raise ipaerror.gen_exception(ipaerror.LDAP_MISSING_DN)
|
||||
|
||||
if opts:
|
||||
self.set_principal(opts['remoteuser'])
|
||||
|
||||
proxydn = self.get_dn_from_principal(self.princ)
|
||||
|
||||
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,proxydn)
|
||||
conn = self.getConnection(opts)
|
||||
try:
|
||||
res = m1.updateEntry(moddn, oldentry, newentry)
|
||||
res = conn.updateEntry(moddn, oldentry, newentry)
|
||||
finally:
|
||||
_LDAPPool.releaseConn(m1)
|
||||
self.releaseConnection(conn)
|
||||
return res
|
||||
|
||||
def __safe_filter(self, criteria):
|
||||
@ -234,8 +292,6 @@ class IPAServer:
|
||||
attribute name and the value is either a string or in the case
|
||||
of a multi-valued field a list of values. user_container sets
|
||||
where in the tree the user is placed."""
|
||||
global _LDAPPool
|
||||
|
||||
if user_container is None:
|
||||
user_container = DefaultUserContainer
|
||||
|
||||
@ -288,16 +344,11 @@ class IPAServer:
|
||||
for u in user:
|
||||
entry.setValues(u, user[u])
|
||||
|
||||
if opts:
|
||||
self.set_principal(opts['remoteuser'])
|
||||
|
||||
dn = self.get_dn_from_principal(self.princ)
|
||||
|
||||
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
|
||||
conn = self.getConnection(opts)
|
||||
try:
|
||||
res = m1.addEntry(entry)
|
||||
res = conn.addEntry(entry)
|
||||
finally:
|
||||
_LDAPPool.releaseConn(m1)
|
||||
self.releaseConnection(conn)
|
||||
return res
|
||||
|
||||
def get_add_schema (self):
|
||||
@ -348,20 +399,13 @@ class IPAServer:
|
||||
"""Return a list containing a User object for each
|
||||
existing user.
|
||||
"""
|
||||
global _LDAPPool
|
||||
|
||||
if opts:
|
||||
self.set_principal(opts['remoteuser'])
|
||||
|
||||
dn = self.get_dn_from_principal(self.princ)
|
||||
|
||||
filter = "(objectclass=posixAccount)"
|
||||
|
||||
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
|
||||
conn = self.getConnection(opts)
|
||||
try:
|
||||
all_users = m1.getList(self.basedn, self.scope, filter, None)
|
||||
all_users = conn.getList(self.basedn, self.scope, filter, None)
|
||||
finally:
|
||||
_LDAPPool.releaseConn(m1)
|
||||
self.releaseConnection(conn)
|
||||
|
||||
users = []
|
||||
for u in all_users:
|
||||
@ -372,13 +416,6 @@ class IPAServer:
|
||||
def find_users (self, criteria, sattrs=None, opts=None):
|
||||
"""Returns a list: counter followed by the results.
|
||||
If the results are truncated, counter will be set to -1."""
|
||||
global _LDAPPool
|
||||
|
||||
if opts:
|
||||
self.set_principal(opts['remoteuser'])
|
||||
|
||||
dn = self.get_dn_from_principal(self.princ)
|
||||
|
||||
# Assume the list of fields to search will come from a central
|
||||
# configuration repository. A good format for that would be
|
||||
# a comma-separated list of fields
|
||||
@ -389,26 +426,26 @@ class IPAServer:
|
||||
criteria_words = re.split(r'\s+', criteria)
|
||||
criteria_words = filter(lambda value:value!="", criteria_words)
|
||||
if len(criteria_words) == 0:
|
||||
return []
|
||||
return [0]
|
||||
|
||||
(exact_match_filter, partial_match_filter) = self.__generate_match_filters(
|
||||
search_fields, criteria_words)
|
||||
|
||||
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
|
||||
conn = self.getConnection(opts)
|
||||
try:
|
||||
try:
|
||||
exact_results = m1.getListAsync(self.basedn, self.scope,
|
||||
exact_results = conn.getListAsync(self.basedn, self.scope,
|
||||
exact_match_filter, sattrs)
|
||||
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
|
||||
exact_results = [0]
|
||||
|
||||
try:
|
||||
partial_results = m1.getListAsync(self.basedn, self.scope,
|
||||
partial_results = conn.getListAsync(self.basedn, self.scope,
|
||||
partial_match_filter, sattrs)
|
||||
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
|
||||
partial_results = [0]
|
||||
finally:
|
||||
_LDAPPool.releaseConn(m1)
|
||||
self.releaseConnection(conn)
|
||||
|
||||
exact_counter = exact_results[0]
|
||||
partial_counter = partial_results[0]
|
||||
@ -450,13 +487,6 @@ class IPAServer:
|
||||
def mark_user_deleted (self, uid, opts=None):
|
||||
"""Mark a user as inactive in LDAP. We aren't actually deleting
|
||||
users here, just making it so they can't log in, etc."""
|
||||
global _LDAPPool
|
||||
|
||||
if opts:
|
||||
self.set_principal(opts['remoteuser'])
|
||||
|
||||
proxydn = self.get_dn_from_principal(self.princ)
|
||||
|
||||
user = self.get_user_by_uid(uid, ['dn', 'uid', 'nsAccountlock'], opts)
|
||||
|
||||
# Are we doing an add or replace operation?
|
||||
@ -467,11 +497,11 @@ class IPAServer:
|
||||
else:
|
||||
has_key = False
|
||||
|
||||
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,proxydn)
|
||||
conn = self.getConnection(opts)
|
||||
try:
|
||||
res = m1.inactivateEntry(user['dn'], has_key)
|
||||
res = conn.inactivateEntry(user['dn'], has_key)
|
||||
finally:
|
||||
_LDAPPool.releaseConn(m1)
|
||||
self.releaseConnection(conn)
|
||||
return res
|
||||
|
||||
def delete_user (self, uid, opts=None):
|
||||
@ -483,18 +513,15 @@ class IPAServer:
|
||||
The memberOf plugin handles removing the user from any other
|
||||
groups.
|
||||
"""
|
||||
if opts:
|
||||
self.set_principal(opts['remoteuser'])
|
||||
|
||||
dn = self.get_dn_from_principal(self.princ)
|
||||
|
||||
user_dn = self.get_user_by_uid(uid, ['dn', 'uid', 'objectclass'], opts)
|
||||
if user_dn is None:
|
||||
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND)
|
||||
|
||||
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
|
||||
res = m1.deleteEntry(user_dn['dn'])
|
||||
_LDAPPool.releaseConn(m1)
|
||||
conn = self.getConnection(opts)
|
||||
try:
|
||||
res = conn.deleteEntry(user_dn['dn'])
|
||||
finally:
|
||||
self.releaseConnection(conn)
|
||||
return res
|
||||
|
||||
# Group support
|
||||
@ -532,8 +559,6 @@ class IPAServer:
|
||||
attribute name and the value is either a string or in the case
|
||||
of a multi-valued field a list of values. group_container sets
|
||||
where in the tree the group is placed."""
|
||||
global _LDAPPool
|
||||
|
||||
if group_container is None:
|
||||
group_container = DefaultGroupContainer
|
||||
|
||||
@ -554,38 +579,26 @@ class IPAServer:
|
||||
for g in group:
|
||||
entry.setValues(g, group[g])
|
||||
|
||||
if opts:
|
||||
self.set_principal(opts['remoteuser'])
|
||||
|
||||
dn = self.get_dn_from_principal(self.princ)
|
||||
|
||||
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
|
||||
conn = self.getConnection(opts)
|
||||
try:
|
||||
res = m1.addEntry(entry)
|
||||
res = conn.addEntry(entry)
|
||||
finally:
|
||||
_LDAPPool.releaseConn(m1)
|
||||
self.releaseConnection(conn)
|
||||
|
||||
def find_groups (self, criteria, sattrs=None, opts=None):
|
||||
"""Return a list containing a User object for each
|
||||
existing group that matches the criteria.
|
||||
"""
|
||||
global _LDAPPool
|
||||
|
||||
if opts:
|
||||
self.set_principal(opts['remoteuser'])
|
||||
|
||||
dn = self.get_dn_from_principal(self.princ)
|
||||
|
||||
criteria = self.__safe_filter(criteria)
|
||||
|
||||
filter = "(&(cn=%s)(objectClass=posixGroup))" % criteria
|
||||
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
|
||||
conn = self.getConnection(opts)
|
||||
try:
|
||||
results = m1.getList(self.basedn, self.scope, filter, sattrs)
|
||||
results = conn.getList(self.basedn, self.scope, filter, sattrs)
|
||||
except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND):
|
||||
results = []
|
||||
finally:
|
||||
_LDAPPool.releaseConn(m1)
|
||||
self.releaseConnection(conn)
|
||||
|
||||
groups = []
|
||||
for u in results:
|
||||
@ -599,9 +612,6 @@ class IPAServer:
|
||||
group is the cn of the group to be added to
|
||||
"""
|
||||
|
||||
if opts:
|
||||
self.set_principal(opts['remoteuser'])
|
||||
|
||||
old_group = self.get_group_by_cn(group, None, opts)
|
||||
if old_group is None:
|
||||
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND)
|
||||
@ -652,9 +662,6 @@ class IPAServer:
|
||||
group is the cn of the group to be removed from
|
||||
"""
|
||||
|
||||
if opts:
|
||||
self.set_principal(opts['remoteuser'])
|
||||
|
||||
old_group = self.get_group_by_cn(group, None, opts)
|
||||
if old_group is None:
|
||||
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND)
|
||||
@ -718,19 +725,16 @@ class IPAServer:
|
||||
The memberOf plugin handles removing the group from any other
|
||||
groups.
|
||||
"""
|
||||
if opts:
|
||||
self.set_principal(opts['remoteuser'])
|
||||
|
||||
dn = self.get_dn_from_principal(self.princ)
|
||||
|
||||
group = self.get_group_by_cn(group_cn, ['dn', 'cn'], opts)
|
||||
|
||||
if len(group) != 1:
|
||||
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND)
|
||||
|
||||
m1 = _LDAPPool.getConn(self.host,self.port,self.bindca,self.bindcert,self.bindkey,dn)
|
||||
res = m1.deleteEntry(group[0]['dn'])
|
||||
_LDAPPool.releaseConn(m1)
|
||||
conn = self.getConnection(opts)
|
||||
try:
|
||||
res = conn.deleteEntry(group[0]['dn'])
|
||||
finally:
|
||||
self.releaseConnection(conn)
|
||||
return res
|
||||
|
||||
def add_group_to_group(self, group, tgroup, opts=None):
|
||||
@ -739,9 +743,6 @@ class IPAServer:
|
||||
tgroup is the cn of the group to be added to
|
||||
"""
|
||||
|
||||
if opts:
|
||||
self.set_principal(opts['remoteuser'])
|
||||
|
||||
old_group = self.get_group_by_cn(tgroup, None, opts)
|
||||
if old_group is None:
|
||||
raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND)
|
||||
|
@ -126,13 +126,19 @@ class ModXMLRPCRequestHandler(object):
|
||||
def register_instance(self,instance):
|
||||
self.register_module(instance)
|
||||
|
||||
def _marshaled_dispatch(self, data, remoteuser):
|
||||
def _marshaled_dispatch(self, data, req):
|
||||
"""Dispatches an XML-RPC method from marshalled (XML) data."""
|
||||
|
||||
params, method = loads(data)
|
||||
|
||||
# Populate the Apache environment variables
|
||||
req.add_common_vars()
|
||||
|
||||
opts={}
|
||||
opts['remoteuser'] = remoteuser
|
||||
opts['remoteuser'] = req.user
|
||||
|
||||
if req.subprocess_env.get("KRB5CCNAME") is not None:
|
||||
opts['keytab'] = req.subprocess_env.get("KRB5CCNAME")
|
||||
|
||||
# Tack onto the end of the passed-in arguments any options we also
|
||||
# need
|
||||
@ -263,7 +269,7 @@ class ModXMLRPCRequestHandler(object):
|
||||
req.allow_methods(['POST'],1)
|
||||
raise apache.SERVER_RETURN, apache.HTTP_METHOD_NOT_ALLOWED
|
||||
|
||||
response = self._marshaled_dispatch(req.read(), req.user)
|
||||
response = self._marshaled_dispatch(req.read(), req)
|
||||
|
||||
req.content_type = "text/xml"
|
||||
req.set_content_length(len(response))
|
||||
|
Loading…
Reference in New Issue
Block a user