2010-01-12 09:40:09 -06:00
# Authors:
# Pavel Zuna <pzuna@redhat.com>
#
# Copyright (C) 2009 Red Hat
# see file 'COPYING' for use and warranty information
#
2010-12-09 06:59:11 -06:00
# 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, either version 3 of the License, or
# (at your option) any later version.
2010-01-12 09:40:09 -06:00
#
# 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
2010-12-09 06:59:11 -06:00
# along with this program. If not, see <http://www.gnu.org/licenses/>.
2011-08-24 21:48:30 -05:00
import re
import ldap as _ldap
from ipalib import api , errors , output
2011-11-21 09:50:27 -06:00
from ipalib import Command , Password , Str , Flag , StrEnum
2011-08-24 21:48:30 -05:00
from ipalib . cli import to_cli
2012-04-16 01:33:26 -05:00
from ipalib . util import validate_dn_param
2011-09-29 04:55:13 -05:00
from ipalib . dn import *
2012-03-22 12:40:54 -05:00
from ipalib . plugins . user import NO_UPG_MAGIC
2011-08-24 21:48:30 -05:00
if api . env . in_server and api . env . context in [ ' lite ' , ' server ' ] :
try :
from ipaserver . plugins . ldap2 import ldap2
except StandardError , e :
raise e
from ipalib import _
__doc__ = _ ( """
2010-01-12 09:40:09 -06:00
Migration to IPA
2010-12-07 10:47:21 -06:00
Migrate users and groups from an LDAP server to IPA .
2010-01-12 09:40:09 -06:00
2010-12-07 10:47:21 -06:00
This performs an LDAP query against the remote server searching for
users and groups in a container . In order to migrate passwords you need
to bind as a user that can read the userPassword attribute on the remote
server . This is generally restricted to high - level admins such as
cn = Directory Manager in 389 - ds ( this is the default bind user ) .
The default user container is ou = People .
The default group container is ou = Groups .
Users and groups that already exist on the IPA server are skipped .
Two LDAP schemas define how group members are stored : RFC2307 and
RFC2307bis . RFC2307bis uses member and uniquemember to specify group
members , RFC2307 uses memberUid . The default schema is RFC2307bis .
2012-03-01 04:41:53 -06:00
The schema compat feature allows IPA to reformat data for systems that
do not support RFC2307bis . It is recommended that this feature is disabled
during migration to reduce system overhead . It can be re - enabled after
migration . To migrate with it enabled use the " --with-compat " option .
2010-12-07 10:47:21 -06:00
Migrated users do not have Kerberos credentials , they have only their
2011-03-04 10:08:54 -06:00
LDAP password . To complete the migration process , users need to go
2010-12-07 10:47:21 -06:00
to http : / / ipa . example . com / ipa / migration and authenticate using their
LDAP password in order to generate their Kerberos credentials .
2011-03-04 10:08:54 -06:00
Migration is disabled by default . Use the command ipa config - mod to
enable it :
2010-12-07 10:47:21 -06:00
ipa config - mod - - enable - migration = TRUE
2012-01-30 15:29:32 -06:00
If a base DN is not provided with - - basedn then IPA will use either
the value of defaultNamingContext if it is set or the first value
in namingContexts set in the root of the remote LDAP server .
2010-12-07 10:47:21 -06:00
EXAMPLES :
2010-12-13 08:48:16 -06:00
The simplest migration , accepting all defaults :
2010-12-07 10:47:21 -06:00
ipa migrate - ds ldap : / / ds . example . com : 389
2012-02-03 02:38:16 -06:00
Specify the user and group container . This can be used to migrate user
and group data from an IPA v1 server :
ipa migrate - ds - - user - container = ' cn=users,cn=accounts ' \\
- - group - container = ' cn=groups,cn=accounts ' \\
ldap : / / ds . example . com : 389
2011-10-03 09:01:01 -05:00
Since IPA v2 server already contain predefined groups that may collide with
2012-02-03 02:38:16 -06:00
groups in migrated ( IPA v1 ) server ( for example admins , ipausers ) , users
having colliding group as their primary group may happen to belong to
an unknown group on new IPA v2 server .
2011-10-03 09:01:01 -05:00
Use - - group - overwrite - gid option to overwrite GID of already existing groups
to prevent this issue :
2012-02-03 02:38:16 -06:00
ipa migrate - ds - - group - overwrite - gid \\
- - user - container = ' cn=users,cn=accounts ' \\
- - group - container = ' cn=groups,cn=accounts ' \\
ldap : / / ds . example . com : 389
Migrated users or groups may have object class and accompanied attributes
unknown to the IPA v2 server . These object classes and attributes may be
left out of the migration process :
ipa migrate - ds - - user - container = ' cn=users,cn=accounts ' \\
- - group - container = ' cn=groups,cn=accounts ' \\
- - user - ignore - objectclass = radiusprofile \\
- - user - ignore - attribute = radiusgroupname \\
ldap : / / ds . example . com : 389
2011-08-24 21:48:30 -05:00
""" )
2010-01-12 09:40:09 -06:00
# USER MIGRATION CALLBACKS AND VARS
2010-03-05 15:11:21 -06:00
_krb_err_msg = _ ( ' Kerberos principal %s already exists. Use \' ipa user-mod \' to set it manually. ' )
_grp_err_msg = _ ( ' Failed to add user to the default group. Use \' ipa group-add-member \' to add manually. ' )
2011-06-01 11:04:24 -05:00
_ref_err_msg = _ ( ' Migration of LDAP search reference is not supported. ' )
2011-09-29 04:55:13 -05:00
_dn_err_msg = _ ( ' Malformed DN ' )
2010-01-12 09:40:09 -06:00
2010-10-26 15:10:42 -05:00
_supported_schemas = ( u ' RFC2307bis ' , u ' RFC2307 ' )
2010-01-12 09:40:09 -06:00
2012-03-01 04:41:53 -06:00
_compat_dn = " cn=Schema Compatibility,cn=plugins,cn=config "
2012-03-20 21:37:27 -05:00
def is_DN_syntax ( ldap , attr ) :
"""
Check the schema to see if the attribute uses DN syntax .
Returns True / False
"""
obj = ldap . schema . get_obj ( _ldap . schema . AttributeType , attr )
return obj is not None and obj . syntax == ' 1.3.6.1.4.1.1466.115.121.1.12 '
2010-10-26 15:10:42 -05:00
def _pre_migrate_user ( ldap , pkey , dn , entry_attrs , failed , config , ctx , * * kwargs ) :
2011-04-08 09:19:42 -05:00
attr_blacklist = [ ' krbprincipalkey ' , ' memberofindirect ' , ' memberindirect ' ]
2011-06-03 07:21:43 -05:00
attr_blacklist . extend ( kwargs . get ( ' attr_blacklist ' , [ ] ) )
2012-03-22 12:40:54 -05:00
ds_ldap = ctx [ ' ds_ldap ' ]
has_upg = ctx [ ' has_upg ' ]
search_bases = kwargs . get ( ' search_bases ' , None )
valid_gids = kwargs [ ' valid_gids ' ]
2010-11-26 08:37:12 -06:00
2012-03-22 12:40:54 -05:00
if ' gidnumber ' not in entry_attrs :
raise errors . NotFound ( reason = _ ( ' %(user)s is not a POSIX user ' ) % dict ( user = pkey ) )
else :
# See if the gidNumber at least points to a valid group on the remote
# server.
if entry_attrs [ ' gidnumber ' ] [ 0 ] not in valid_gids :
try :
( remote_dn , remote_entry ) = ds_ldap . find_entry_by_attr (
' gidnumber ' , entry_attrs [ ' gidnumber ' ] [ 0 ] , ' posixgroup ' ,
[ ' ' ] , search_bases [ ' group ' ]
)
valid_gids . append ( entry_attrs [ ' gidnumber ' ] [ 0 ] )
except errors . NotFound :
api . log . warn ( ' Migrated user \' s GID number %s does not point to a known group. ' % entry_attrs [ ' gidnumber ' ] [ 0 ] )
# We don't want to create a UPG so set the magic value in description
# to let the DS plugin know.
entry_attrs . setdefault ( ' description ' , [ ] )
entry_attrs [ ' description ' ] . append ( NO_UPG_MAGIC )
2010-01-12 09:40:09 -06:00
# fill in required attributes by IPA
2010-10-26 09:26:06 -05:00
entry_attrs [ ' ipauniqueid ' ] = ' autogenerate '
2010-01-12 09:40:09 -06:00
if ' homedirectory ' not in entry_attrs :
homes_root = config . get ( ' ipahomesrootdir ' , ( ' /home ' , ) ) [ 0 ]
home_dir = ' %s / %s ' % ( homes_root , pkey )
home_dir = home_dir . replace ( ' // ' , ' / ' ) . rstrip ( ' / ' )
entry_attrs [ ' homedirectory ' ] = home_dir
2012-03-22 12:40:54 -05:00
if ' loginshell ' not in entry_attrs :
default_shell = config . get ( ' ipadefaultloginshell ' , [ ' /bin/sh ' ] ) [ 0 ]
entry_attrs . setdefault ( ' loginshell ' , default_shell )
2010-01-12 09:40:09 -06:00
2011-04-08 09:19:42 -05:00
# do not migrate all attributes
2010-11-26 08:37:12 -06:00
for attr in entry_attrs . keys ( ) :
if attr in attr_blacklist :
del entry_attrs [ attr ]
2011-06-03 07:21:43 -05:00
# do not migrate all object classes
if ' objectclass ' in entry_attrs :
for object_class in kwargs . get ( ' oc_blacklist ' , [ ] ) :
try :
entry_attrs [ ' objectclass ' ] . remove ( object_class )
except ValueError : # object class not present
pass
2010-01-12 09:40:09 -06:00
# generate a principal name and check if it isn't already taken
2010-02-17 11:32:58 -06:00
principal = u ' %s @ %s ' % ( pkey , api . env . realm )
2010-01-12 09:40:09 -06:00
try :
ldap . find_entry_by_attr (
' krbprincipalname ' , principal , ' krbprincipalaux ' , [ ' ' ]
)
except errors . NotFound :
entry_attrs [ ' krbprincipalname ' ] = principal
else :
2011-06-01 11:04:24 -05:00
failed [ pkey ] = unicode ( _krb_err_msg % principal )
2010-02-19 10:08:16 -06:00
2012-03-20 21:37:27 -05:00
# Fix any attributes with DN syntax that point to entries in the old
# tree
for attr in entry_attrs . keys ( ) :
if is_DN_syntax ( ldap , attr ) :
for ind , value in enumerate ( entry_attrs [ attr ] ) :
try :
( remote_dn , remote_entry ) = ds_ldap . get_entry ( value , [ api . Object . user . primary_key . name , api . Object . group . primary_key . name ] )
except errors . NotFound :
2012-03-22 12:40:54 -05:00
api . log . warn ( ' %s : attribute %s refers to non-existent entry %s ' % ( pkey , attr , value ) )
2012-03-20 21:37:27 -05:00
continue
if value . lower ( ) . endswith ( search_bases [ ' user ' ] ) :
primary_key = api . Object . user . primary_key . name
container = api . env . container_user
elif value . lower ( ) . endswith ( search_bases [ ' group ' ] ) :
primary_key = api . Object . group . primary_key . name
container = api . env . container_group
else :
2012-03-22 12:40:54 -05:00
api . log . warn ( ' %s : value %s in attribute %s does not belong into any known container ' % ( pkey , value , attr ) )
2012-03-20 21:37:27 -05:00
continue
if not remote_entry . get ( primary_key ) :
2012-03-22 12:40:54 -05:00
api . log . warn ( ' %s : there is no primary key %s to migrate for %s ' % ( pkey , primary_key , attr ) )
2012-03-20 21:37:27 -05:00
continue
2012-03-22 12:40:54 -05:00
api . log . debug ( ' converting DN value %s for %s in %s ' % ( value , attr , dn ) )
2012-03-20 21:37:27 -05:00
rdnval = remote_entry [ primary_key ] [ 0 ] . lower ( )
entry_attrs [ attr ] [ ind ] = \
str ( DN ( ( primary_key , rdnval ) ,
container ,
api . env . basedn ) )
2010-01-12 09:40:09 -06:00
return dn
def _post_migrate_user ( ldap , pkey , dn , entry_attrs , failed , config , ctx ) :
# add user to the default group
try :
ldap . add_entry_to_group ( dn , ctx [ ' def_group_dn ' ] )
except errors . ExecutionError , e :
2011-06-01 11:04:24 -05:00
failed [ pkey ] = unicode ( _grp_err_msg )
2012-03-22 12:40:54 -05:00
if ' description ' in entry_attrs and NO_UPG_MAGIC in entry_attrs [ ' description ' ] :
entry_attrs [ ' description ' ] . remove ( NO_UPG_MAGIC )
kw = { ' setattr ' : unicode ( ' description= %s ' % ' , ' . join ( entry_attrs [ ' description ' ] ) ) }
try :
api . Command [ ' user_mod ' ] ( pkey , * * kw )
except ( errors . EmptyModlist , errors . NotFound ) :
pass
2010-01-12 09:40:09 -06:00
# GROUP MIGRATION CALLBACKS AND VARS
2010-10-26 15:10:42 -05:00
def _pre_migrate_group ( ldap , pkey , dn , entry_attrs , failed , config , ctx , * * kwargs ) :
def convert_members_rfc2307bis ( member_attr , search_bases , overwrite = False ) :
2010-01-12 09:40:09 -06:00
"""
Convert DNs in member attributes to work in IPA .
"""
new_members = [ ]
entry_attrs . setdefault ( member_attr , [ ] )
2010-02-19 10:08:16 -06:00
for m in entry_attrs [ member_attr ] :
2010-10-26 15:10:42 -05:00
try :
# what str2dn returns looks like [[('cn', 'foo', 4)], [('dc', 'example', 1)], [('dc', 'com', 1)]]
rdn = _ldap . dn . str2dn ( m , flags = _ldap . DN_FORMAT_LDAPV3 ) [ 0 ]
rdnval = rdn [ 0 ] [ 1 ]
except IndexError :
api . log . error ( ' Malformed DN %s has no RDN? ' % m )
continue
if m . lower ( ) . endswith ( search_bases [ ' user ' ] ) :
api . log . info ( ' migrating user %s ' % m )
m = ' %s = %s , %s ' % ( api . Object . user . primary_key . name ,
rdnval ,
api . env . container_user )
elif m . lower ( ) . endswith ( search_bases [ ' group ' ] ) :
api . log . info ( ' migrating group %s ' % m )
m = ' %s = %s , %s ' % ( api . Object . group . primary_key . name ,
rdnval ,
api . env . container_group )
else :
api . log . error ( ' entry %s does not belong into any known container ' % m )
2010-01-12 09:40:09 -06:00
continue
2010-10-26 15:10:42 -05:00
2010-01-12 09:40:09 -06:00
m = ldap . normalize_dn ( m )
new_members . append ( m )
2010-10-26 15:10:42 -05:00
2010-01-12 09:40:09 -06:00
del entry_attrs [ member_attr ]
if overwrite :
entry_attrs [ ' member ' ] = [ ]
entry_attrs [ ' member ' ] + = new_members
2010-10-26 15:10:42 -05:00
def convert_members_rfc2307 ( member_attr ) :
"""
Convert usernames in member attributes to work in IPA .
"""
new_members = [ ]
entry_attrs . setdefault ( member_attr , [ ] )
for m in entry_attrs [ member_attr ] :
memberdn = ' %s = %s , %s ' % ( api . Object . user . primary_key . name ,
m ,
api . env . container_user )
new_members . append ( ldap . normalize_dn ( memberdn ) )
entry_attrs [ ' member ' ] = new_members
2011-04-08 09:19:42 -05:00
attr_blacklist = [ ' memberofindirect ' , ' memberindirect ' ]
2011-06-03 07:21:43 -05:00
attr_blacklist . extend ( kwargs . get ( ' attr_blacklist ' , [ ] ) )
2011-04-08 09:19:42 -05:00
2010-10-26 15:10:42 -05:00
schema = kwargs . get ( ' schema ' , None )
2010-10-26 09:26:06 -05:00
entry_attrs [ ' ipauniqueid ' ] = ' autogenerate '
2010-10-26 15:10:42 -05:00
if schema == ' RFC2307bis ' :
search_bases = kwargs . get ( ' search_bases ' , None )
if not search_bases :
raise ValueError ( ' Search bases not specified ' )
convert_members_rfc2307bis ( ' member ' , search_bases , overwrite = True )
convert_members_rfc2307bis ( ' uniquemember ' , search_bases )
elif schema == ' RFC2307 ' :
convert_members_rfc2307 ( ' memberuid ' )
else :
raise ValueError ( ' Schema %s not supported ' % schema )
2010-01-12 09:40:09 -06:00
2011-04-08 09:19:42 -05:00
# do not migrate all attributes
for attr in entry_attrs . keys ( ) :
if attr in attr_blacklist :
del entry_attrs [ attr ]
2011-06-03 07:21:43 -05:00
# do not migrate all object classes
if ' objectclass ' in entry_attrs :
for object_class in kwargs . get ( ' oc_blacklist ' , [ ] ) :
try :
entry_attrs [ ' objectclass ' ] . remove ( object_class )
except ValueError : # object class not present
pass
2010-01-12 09:40:09 -06:00
return dn
2011-10-03 09:01:01 -05:00
def _group_exc_callback ( ldap , dn , entry_attrs , exc , options ) :
if isinstance ( exc , errors . DuplicateEntry ) :
if options . get ( ' groupoverwritegid ' , False ) and \
entry_attrs . get ( ' gidnumber ' ) is not None :
try :
new_entry_attrs = { ' gidnumber ' : entry_attrs [ ' gidnumber ' ] }
ldap . update_entry ( dn , new_entry_attrs )
except errors . EmptyModlist :
# no change to the GID
pass
# mark as success
return
elif not options . get ( ' groupoverwritegid ' , False ) and \
entry_attrs . get ( ' gidnumber ' ) is not None :
msg = unicode ( exc )
# add information about possibility to overwrite GID
msg = msg + unicode ( _ ( ' . Check GID of the existing group. ' \
' Use --group-overwrite-gid option to overwrite the GID ' ) )
raise errors . DuplicateEntry ( message = msg )
raise exc
2010-01-12 09:40:09 -06:00
# DS MIGRATION PLUGIN
2010-10-26 15:10:42 -05:00
def construct_filter ( template , oc_list ) :
oc_subfilter = ' ' . join ( [ ' (objectclass= %s ) ' % oc for oc in oc_list ] )
return template % oc_subfilter
2010-01-12 09:40:09 -06:00
def validate_ldapuri ( ugettext , ldapuri ) :
m = re . match ( ' ^ldaps?://[- \ w \ .]+(: \ d+)?$ ' , ldapuri )
if not m :
2010-12-02 15:31:42 -06:00
err_msg = _ ( ' Invalid LDAP URI. ' )
2010-01-12 09:40:09 -06:00
raise errors . ValidationError ( name = ' ldap_uri ' , error = err_msg )
class migrate_ds ( Command ) :
2011-08-24 21:48:30 -05:00
__doc__ = _ ( ' Migrate users and groups from DS to IPA. ' )
2010-01-12 09:40:09 -06:00
migrate_objects = {
# OBJECT_NAME: (search_filter, pre_callback, post_callback)
#
# OBJECT_NAME - is the name of an LDAPObject subclass
# search_filter - is the filter to retrieve objects from DS
# pre_callback - is called for each object just after it was
# retrieved from DS and before being added to IPA
# post_callback - is called for each object after it was added to IPA
2011-10-03 09:01:01 -05:00
# exc_callback - is called when adding entry to IPA raises an exception
2010-01-12 09:40:09 -06:00
#
# {pre, post}_callback parameters:
# ldap - ldap2 instance connected to IPA
# pkey - primary key value of the object (uid for users, etc.)
# dn - dn of the object as it (will be/is) stored in IPA
# entry_attrs - attributes of the object
# failed - a list of so-far failed objects
# config - IPA config entry attributes
# ctx - object context, used to pass data between callbacks
#
# If pre_callback return value evaluates to False, migration
# of the current object is aborted.
2010-10-26 15:10:42 -05:00
' user ' : {
' filter_template ' : ' (&(| %s )(uid=*)) ' ,
' oc_option ' : ' userobjectclass ' ,
2011-06-03 07:21:43 -05:00
' oc_blacklist_option ' : ' userignoreobjectclass ' ,
' attr_blacklist_option ' : ' userignoreattribute ' ,
2010-10-26 15:10:42 -05:00
' pre_callback ' : _pre_migrate_user ,
2011-10-03 09:01:01 -05:00
' post_callback ' : _post_migrate_user ,
' exc_callback ' : None
2010-10-26 15:10:42 -05:00
} ,
' group ' : {
' filter_template ' : ' (&(| %s )(cn=*)) ' ,
' oc_option ' : ' groupobjectclass ' ,
2011-06-03 07:21:43 -05:00
' oc_blacklist_option ' : ' groupignoreobjectclass ' ,
' attr_blacklist_option ' : ' groupignoreattribute ' ,
2010-10-26 15:10:42 -05:00
' pre_callback ' : _pre_migrate_group ,
2011-10-03 09:01:01 -05:00
' post_callback ' : None ,
' exc_callback ' : _group_exc_callback ,
2010-10-26 15:10:42 -05:00
} ,
2010-01-12 09:40:09 -06:00
}
migrate_order = ( ' user ' , ' group ' )
takes_args = (
Str ( ' ldapuri ' , validate_ldapuri ,
cli_name = ' ldap_uri ' ,
2010-02-19 10:08:16 -06:00
label = _ ( ' LDAP URI ' ) ,
doc = _ ( ' LDAP URI of DS server to migrate from ' ) ,
2010-01-12 09:40:09 -06:00
) ,
Password ( ' bindpw ' ,
cli_name = ' password ' ,
2010-12-02 15:31:42 -06:00
label = _ ( ' Password ' ) ,
2011-10-03 09:01:01 -05:00
confirm = False ,
2010-03-05 15:11:21 -06:00
doc = _ ( ' bind password ' ) ,
2010-01-12 09:40:09 -06:00
) ,
)
takes_options = (
2012-04-16 01:33:26 -05:00
Str ( ' binddn? ' , validate_dn_param ,
2010-01-12 09:40:09 -06:00
cli_name = ' bind_dn ' ,
2010-02-19 10:08:16 -06:00
label = _ ( ' Bind DN ' ) ,
2010-01-12 09:40:09 -06:00
default = u ' cn=directory manager ' ,
autofill = True ,
) ,
2012-04-16 01:33:26 -05:00
Str ( ' usercontainer ' , validate_dn_param ,
2010-01-12 09:40:09 -06:00
cli_name = ' user_container ' ,
2010-02-19 10:08:16 -06:00
label = _ ( ' User container ' ) ,
2012-04-16 01:33:26 -05:00
doc = _ ( ' DN of container for users in DS relative to base DN ' ) ,
2010-01-12 09:40:09 -06:00
default = u ' ou=people ' ,
autofill = True ,
) ,
2012-04-16 01:33:26 -05:00
Str ( ' groupcontainer ' , validate_dn_param ,
2010-01-12 09:40:09 -06:00
cli_name = ' group_container ' ,
2010-02-19 10:08:16 -06:00
label = _ ( ' Group container ' ) ,
2012-04-16 01:33:26 -05:00
doc = _ ( ' DN of container for groups in DS relative to base DN ' ) ,
2010-01-12 09:40:09 -06:00
default = u ' ou=groups ' ,
autofill = True ,
) ,
2011-11-21 09:50:27 -06:00
Str ( ' userobjectclass* ' ,
2010-10-26 15:10:42 -05:00
cli_name = ' user_objectclass ' ,
label = _ ( ' User object class ' ) ,
doc = _ ( ' Comma-separated list of objectclasses used to search for user entries in DS ' ) ,
2011-11-21 09:50:27 -06:00
csv = True ,
2010-10-26 15:10:42 -05:00
default = ( u ' person ' , ) ,
autofill = True ,
) ,
2011-11-21 09:50:27 -06:00
Str ( ' groupobjectclass* ' ,
2010-10-26 15:10:42 -05:00
cli_name = ' group_objectclass ' ,
label = _ ( ' Group object class ' ) ,
doc = _ ( ' Comma-separated list of objectclasses used to search for group entries in DS ' ) ,
2011-11-21 09:50:27 -06:00
csv = True ,
2010-10-26 15:10:42 -05:00
default = ( u ' groupOfUniqueNames ' , u ' groupOfNames ' ) ,
autofill = True ,
) ,
2011-11-21 09:50:27 -06:00
Str ( ' userignoreobjectclass* ' ,
2011-06-03 07:21:43 -05:00
cli_name = ' user_ignore_objectclass ' ,
label = _ ( ' Ignore user object class ' ) ,
doc = _ ( ' Comma-separated list of objectclasses to be ignored for user entries in DS ' ) ,
2011-11-21 09:50:27 -06:00
csv = True ,
2011-06-03 07:21:43 -05:00
default = tuple ( ) ,
autofill = True ,
) ,
2011-11-21 09:50:27 -06:00
Str ( ' userignoreattribute* ' ,
2011-06-03 07:21:43 -05:00
cli_name = ' user_ignore_attribute ' ,
label = _ ( ' Ignore user attribute ' ) ,
doc = _ ( ' Comma-separated list of attributes to be ignored for user entries in DS ' ) ,
2011-11-21 09:50:27 -06:00
csv = True ,
2011-06-03 07:21:43 -05:00
default = tuple ( ) ,
autofill = True ,
) ,
2011-11-21 09:50:27 -06:00
Str ( ' groupignoreobjectclass* ' ,
2011-06-03 07:21:43 -05:00
cli_name = ' group_ignore_objectclass ' ,
label = _ ( ' Ignore group object class ' ) ,
doc = _ ( ' Comma-separated list of objectclasses to be ignored for group entries in DS ' ) ,
2011-11-21 09:50:27 -06:00
csv = True ,
2011-06-03 07:21:43 -05:00
default = tuple ( ) ,
autofill = True ,
) ,
2011-11-21 09:50:27 -06:00
Str ( ' groupignoreattribute* ' ,
2011-06-03 07:21:43 -05:00
cli_name = ' group_ignore_attribute ' ,
label = _ ( ' Ignore group attribute ' ) ,
doc = _ ( ' Comma-separated list of attributes to be ignored for group entries in DS ' ) ,
2011-11-21 09:50:27 -06:00
csv = True ,
2011-06-03 07:21:43 -05:00
default = tuple ( ) ,
autofill = True ,
) ,
2011-10-03 09:01:01 -05:00
Flag ( ' groupoverwritegid ' ,
cli_name = ' group_overwrite_gid ' ,
label = _ ( ' Overwrite GID ' ) ,
doc = _ ( ' When migrating a group already existing in IPA domain overwrite the ' \
' group GID and report as success ' ) ,
) ,
2010-10-26 15:10:42 -05:00
StrEnum ( ' schema? ' ,
cli_name = ' schema ' ,
label = _ ( ' LDAP schema ' ) ,
doc = _ ( ' The schema used on the LDAP server. Supported values are RFC2307 and RFC2307bis. The default is RFC2307bis ' ) ,
values = _supported_schemas ,
default = _supported_schemas [ 0 ] ,
autofill = True ,
) ,
2010-09-27 12:50:54 -05:00
Flag ( ' continue? ' ,
2011-10-03 09:01:01 -05:00
label = _ ( ' Continue ' ) ,
2011-02-02 10:15:35 -06:00
doc = _ ( ' Continuous operation mode. Errors are reported but the process continues ' ) ,
2010-09-27 12:50:54 -05:00
default = False ,
) ,
2012-01-30 15:29:32 -06:00
Str ( ' basedn? ' ,
cli_name = ' base_dn ' ,
label = _ ( ' Base DN ' ) ,
doc = _ ( ' Base DN on remote LDAP server ' ) ,
) ,
2012-03-01 04:41:53 -06:00
Flag ( ' compat? ' ,
cli_name = ' with_compat ' ,
label = _ ( ' Ignore compat plugin ' ) ,
doc = _ ( ' Allows migration despite the usage of compat plugin ' ) ,
default = False ,
) ,
2010-01-12 09:40:09 -06:00
)
has_output = (
output . Output ( ' result ' ,
type = dict ,
2010-03-05 15:11:21 -06:00
doc = _ ( ' Lists of objects migrated; categorized by type. ' ) ,
2010-01-12 09:40:09 -06:00
) ,
output . Output ( ' failed ' ,
type = dict ,
2010-03-05 15:11:21 -06:00
doc = _ ( ' Lists of objects that could not be migrated; categorized by type. ' ) ,
2010-01-12 09:40:09 -06:00
) ,
output . Output ( ' enabled ' ,
type = bool ,
2010-03-05 15:11:21 -06:00
doc = _ ( ' False if migration mode was disabled. ' ) ,
2010-01-12 09:40:09 -06:00
) ,
2012-03-01 04:41:53 -06:00
output . Output ( ' compat ' ,
type = bool ,
doc = _ ( ' False if migration fails because the compatibility plug-in is enabled. ' ) ,
) ,
2010-01-12 09:40:09 -06:00
)
2010-03-05 15:11:21 -06:00
exclude_doc = _ ( ' comma-separated list of %s to exclude from migration ' )
truncated_err_msg = _ ( ''' \
search results for objects to be migrated
have been truncated by the server ;
2011-02-02 10:15:35 -06:00
migration process might be incomplete \n ''' )
2010-03-05 15:11:21 -06:00
migration_disabled_msg = _ ( ''' \
Migration mode is disabled . Use \' ipa config-mod \' to enable it. ' ' ' )
pwd_migration_msg = _ ( ''' \
Passwords have been migrated in pre - hashed format .
IPA is unable to generate Kerberos keys unless provided
with clear text passwords . All migrated users need to
login at https : / / your . domain / ipa / migration / before they
can use their Kerberos accounts . ''' )
2010-01-12 09:40:09 -06:00
def get_options ( self ) :
"""
Call get_options of the baseclass and add " exclude " options
for each type of object being migrated .
"""
for option in super ( migrate_ds , self ) . get_options ( ) :
yield option
for ldap_obj_name in self . migrate_objects :
ldap_obj = self . api . Object [ ldap_obj_name ]
name = ' exclude_ %s s ' % to_cli ( ldap_obj_name )
2011-06-03 07:21:43 -05:00
doc = self . exclude_doc % ldap_obj . object_name_plural
2011-11-21 09:50:27 -06:00
yield Str (
' %s * ' % name , cli_name = name , doc = doc , csv = True ,
default = tuple ( ) , autofill = True
2010-01-12 09:40:09 -06:00
)
def normalize_options ( self , options ) :
"""
Convert all " exclude " option values to lower - case .
Also , empty List parameters are converted to None , but the migration
plugin doesn ' t like that - convert back to empty lists.
"""
for p in self . params ( ) :
2011-11-21 09:50:27 -06:00
if p . csv :
2010-01-12 09:40:09 -06:00
if options [ p . name ] :
options [ p . name ] = tuple (
v . lower ( ) for v in options [ p . name ]
)
else :
options [ p . name ] = tuple ( )
2010-10-26 15:10:42 -05:00
def _get_search_bases ( self , options , ds_base_dn , migrate_order ) :
search_bases = dict ( )
2012-04-16 17:48:57 -05:00
ds_base_dn = DN ( ds_base_dn )
2010-10-26 15:10:42 -05:00
for ldap_obj_name in migrate_order :
2012-04-16 01:33:26 -05:00
container = options . get ( ' %s container ' % to_cli ( ldap_obj_name ) )
if container :
2012-04-16 17:48:57 -05:00
container = DN ( container )
# Don't append base dn if user already appended it in the container dn
if container . endswith ( ds_base_dn ) :
search_base = container
else :
search_base = DN ( container , ds_base_dn )
2012-04-16 01:33:26 -05:00
else :
search_base = ds_base_dn
2012-04-16 17:48:57 -05:00
search_bases [ ldap_obj_name ] = str ( search_base )
2010-10-26 15:10:42 -05:00
return search_bases
2010-01-12 09:40:09 -06:00
def migrate ( self , ldap , config , ds_ldap , ds_base_dn , options ) :
"""
Migrate objects from DS to LDAP .
"""
migrated = { } # {'OBJ': ['PKEY1', 'PKEY2', ...], ...}
failed = { } # {'OBJ': {'PKEY1': 'Failed 'cos blabla', ...}, ...}
2010-10-26 15:10:42 -05:00
search_bases = self . _get_search_bases ( options , ds_base_dn , self . migrate_order )
2010-01-12 09:40:09 -06:00
for ldap_obj_name in self . migrate_order :
ldap_obj = self . api . Object [ ldap_obj_name ]
2010-10-26 15:10:42 -05:00
search_filter = construct_filter ( self . migrate_objects [ ldap_obj_name ] [ ' filter_template ' ] ,
options [ to_cli ( self . migrate_objects [ ldap_obj_name ] [ ' oc_option ' ] ) ] )
2010-01-12 09:40:09 -06:00
exclude = options [ ' exclude_ %s s ' % to_cli ( ldap_obj_name ) ]
2012-03-20 21:37:27 -05:00
context = dict ( ds_ldap = ds_ldap )
2010-01-12 09:40:09 -06:00
migrated [ ldap_obj_name ] = [ ]
failed [ ldap_obj_name ] = { }
2010-09-27 12:50:54 -05:00
try :
( entries , truncated ) = ds_ldap . find_entries (
2011-04-08 09:19:42 -05:00
search_filter , [ ' * ' ] , search_bases [ ldap_obj_name ] ,
2012-01-30 15:29:32 -06:00
_ldap . SCOPE_ONELEVEL ,
2011-06-01 11:04:24 -05:00
time_limit = 0 , size_limit = - 1 ,
search_refs = True # migrated DS may contain search references
2010-09-27 12:50:54 -05:00
)
except errors . NotFound :
if not options . get ( ' continue ' , False ) :
2011-02-23 15:47:49 -06:00
raise errors . NotFound (
2012-01-30 15:29:32 -06:00
reason = _ ( ' Container for %(container)s not found at %(search_base)s ' ) % { ' container ' : ldap_obj_name , ' search_base ' : search_bases [ ldap_obj_name ] }
2011-02-23 15:47:49 -06:00
)
2010-09-27 12:50:54 -05:00
else :
truncated = False
entries = [ ]
2010-01-12 09:40:09 -06:00
if truncated :
self . log . error (
' %s : %s ' % (
2011-07-12 11:01:25 -05:00
ldap_obj . name , self . truncated_err_msg
2010-01-12 09:40:09 -06:00
)
)
2011-06-03 07:21:43 -05:00
blacklists = { }
for blacklist in ( ' oc_blacklist ' , ' attr_blacklist ' ) :
blacklist_option = self . migrate_objects [ ldap_obj_name ] [ blacklist + ' _option ' ]
if blacklist_option is not None :
blacklists [ blacklist ] = options . get ( blacklist_option , tuple ( ) )
else :
blacklists [ blacklist ] = tuple ( )
2012-03-22 12:40:54 -05:00
# get default primary group for new users
if ' def_group_dn ' not in context :
def_group = config . get ( ' ipadefaultprimarygroup ' )
context [ ' def_group_dn ' ] = api . Object . group . get_dn ( def_group )
try :
( g_dn , g_attrs ) = ldap . get_entry ( context [ ' def_group_dn ' ] , [ ' gidnumber ' , ' cn ' ] )
except errors . NotFound :
error_msg = _ ( ' Default group for new users not found ' )
raise errors . NotFound ( reason = error_msg )
if ' gidnumber ' in g_attrs :
context [ ' def_group_gid ' ] = g_attrs [ ' gidnumber ' ] [ 0 ]
context [ ' has_upg ' ] = ldap . has_upg ( )
valid_gids = [ ]
2010-01-12 09:40:09 -06:00
for ( dn , entry_attrs ) in entries :
2011-06-01 11:04:24 -05:00
if dn is None : # LDAP search reference
failed [ ldap_obj_name ] [ entry_attrs [ 0 ] ] = unicode ( _ref_err_msg )
continue
2011-09-29 04:55:13 -05:00
try :
dn = DN ( dn )
except ValueError :
failed [ ldap_obj_name ] [ dn ] = unicode ( _dn_err_msg )
continue
ava = dn [ 0 ] [ 0 ]
if ava . attr == ldap_obj . primary_key . name :
# In case if pkey attribute is in the migrated object DN
# and the original LDAP is multivalued, make sure that
# we pick the correct value (the unique one stored in DN)
pkey = ava . value . lower ( )
else :
pkey = entry_attrs [ ldap_obj . primary_key . name ] [ 0 ] . lower ( )
2010-01-12 09:40:09 -06:00
if pkey in exclude :
continue
dn = ldap_obj . get_dn ( pkey )
entry_attrs [ ' objectclass ' ] = list (
set (
config . get (
ldap_obj . object_class_config , ldap_obj . object_class
) + [ o . lower ( ) for o in entry_attrs [ ' objectclass ' ] ]
)
)
2012-03-20 21:50:17 -05:00
entry_attrs [ ldap_obj . primary_key . name ] [ 0 ] = entry_attrs [ ldap_obj . primary_key . name ] [ 0 ] . lower ( )
2010-01-12 09:40:09 -06:00
2010-10-26 15:10:42 -05:00
callback = self . migrate_objects [ ldap_obj_name ] [ ' pre_callback ' ]
2010-01-12 09:40:09 -06:00
if callable ( callback ) :
2012-03-22 12:40:54 -05:00
try :
dn = callback (
ldap , pkey , dn , entry_attrs , failed [ ldap_obj_name ] ,
config , context , schema = options [ ' schema ' ] ,
search_bases = search_bases ,
valid_gids = valid_gids ,
* * blacklists
)
if not dn :
continue
except errors . NotFound , e :
failed [ ldap_obj_name ] [ pkey ] = unicode ( e . reason )
2010-01-12 09:40:09 -06:00
continue
try :
ldap . add_entry ( dn , entry_attrs )
except errors . ExecutionError , e :
2011-10-03 09:01:01 -05:00
callback = self . migrate_objects [ ldap_obj_name ] [ ' exc_callback ' ]
2010-01-12 09:40:09 -06:00
if callable ( callback ) :
2011-10-03 09:01:01 -05:00
try :
callback ( ldap , dn , entry_attrs , e , options )
except errors . ExecutionError , e :
failed [ ldap_obj_name ] [ pkey ] = unicode ( e )
continue
else :
failed [ ldap_obj_name ] [ pkey ] = unicode ( e )
continue
migrated [ ldap_obj_name ] . append ( pkey )
callback = self . migrate_objects [ ldap_obj_name ] [ ' post_callback ' ]
if callable ( callback ) :
callback (
ldap , pkey , dn , entry_attrs , failed [ ldap_obj_name ] ,
config , context ,
)
2010-01-12 09:40:09 -06:00
return ( migrated , failed )
def execute ( self , ldapuri , bindpw , * * options ) :
ldap = self . api . Backend . ldap2
self . normalize_options ( options )
config = ldap . get_ipa_config ( ) [ 1 ]
2012-01-30 15:29:32 -06:00
ds_base_dn = options . get ( ' basedn ' )
2010-01-12 09:40:09 -06:00
# check if migration mode is enabled
if config . get ( ' ipamigrationenabled ' , ( ' FALSE ' , ) ) [ 0 ] == ' FALSE ' :
2012-03-01 04:41:53 -06:00
return dict ( result = { } , failed = { } , enabled = False , compat = True )
2010-01-12 09:40:09 -06:00
# connect to DS
ds_ldap = ldap2 ( shared_instance = False , ldap_uri = ldapuri , base_dn = ' ' )
ds_ldap . connect ( bind_dn = options [ ' binddn ' ] , bind_pw = bindpw )
2012-03-01 04:41:53 -06:00
#check whether the compat plugin is enabled
if not options . get ( ' compat ' ) :
2012-03-09 05:36:03 -06:00
try :
( dn , check_compat ) = ldap . get_entry ( _compat_dn , normalize = False )
if check_compat is not None and \
check_compat . get ( ' nsslapd-pluginenabled ' , [ ' ' ] ) [ 0 ] . lower ( ) == ' on ' :
2012-03-22 12:40:54 -05:00
return dict ( result = { } , failed = { } , enabled = True , compat = False )
2012-03-09 05:36:03 -06:00
except errors . NotFound :
pass
2012-03-01 04:41:53 -06:00
2012-01-30 15:29:32 -06:00
if not ds_base_dn :
# retrieve base DN from remote LDAP server
( entries , truncated ) = ds_ldap . find_entries (
' ' , [ ' namingcontexts ' , ' defaultnamingcontext ' ] , ' ' ,
_ldap . SCOPE_BASE , size_limit = - 1 , time_limit = 0 ,
)
if ' defaultnamingcontext ' in entries [ 0 ] [ 1 ] :
ds_base_dn = entries [ 0 ] [ 1 ] [ ' defaultnamingcontext ' ] [ 0 ]
else :
try :
ds_base_dn = entries [ 0 ] [ 1 ] [ ' namingcontexts ' ] [ 0 ]
except ( IndexError , KeyError ) , e :
raise StandardError ( str ( e ) )
2010-01-12 09:40:09 -06:00
# migrate!
( migrated , failed ) = self . migrate (
ldap , config , ds_ldap , ds_base_dn , options
)
2012-03-01 04:41:53 -06:00
return dict ( result = migrated , failed = failed , enabled = True , compat = True )
2010-01-12 09:40:09 -06:00
def output_for_cli ( self , textui , result , ldapuri , bindpw , * * options ) :
textui . print_name ( self . name )
if not result [ ' enabled ' ] :
textui . print_plain ( self . migration_disabled_msg )
return 1
2012-03-01 04:41:53 -06:00
if not result [ ' compat ' ] :
textui . print_plain ( " The compat plug-in is enabled. This can increase the memory requirements during migration. Disable the compat plug-in with \' ipa-compat-manage disable \' or re-run this script with \' --with-compat \' option. " )
return 1
2010-01-12 09:40:09 -06:00
textui . print_plain ( ' Migrated: ' )
textui . print_entry1 (
result [ ' result ' ] , attr_order = self . migrate_order ,
one_value_per_line = False
)
for ldap_obj_name in self . migrate_order :
textui . print_plain ( ' Failed %s : ' % ldap_obj_name )
textui . print_entry1 (
result [ ' failed ' ] [ ldap_obj_name ] , attr_order = self . migrate_order ,
one_value_per_line = True ,
)
textui . print_plain ( ' - ' * len ( self . name ) )
2010-09-27 12:50:54 -05:00
textui . print_plain ( unicode ( self . pwd_migration_msg ) )
2010-01-12 09:40:09 -06:00
api . register ( migrate_ds )