Author: jsuchome Date: Fri Apr 11 15:53:55 2008 New Revision: 46421 URL: http://svn.opensuse.org/viewcvs/yast?rev=46421&view=rev Log: - prepare API for "Import User From Previous Installation" in the 1st stage of installation (fate#302980) - 2.16.24 Modified: trunk/users/VERSION trunk/users/package/yast2-users.changes trunk/users/src/UsersSimple.pm trunk/users/src/inst_user.ycp trunk/users/src/inst_user_first.ycp Modified: trunk/users/VERSION URL: http://svn.opensuse.org/viewcvs/yast/trunk/users/VERSION?rev=46421&r1=46... ============================================================================== --- trunk/users/VERSION (original) +++ trunk/users/VERSION Fri Apr 11 15:53:55 2008 @@ -1 +1 @@ -2.16.23 +2.16.24 Modified: trunk/users/package/yast2-users.changes URL: http://svn.opensuse.org/viewcvs/yast/trunk/users/package/yast2-users.changes... ============================================================================== --- trunk/users/package/yast2-users.changes (original) +++ trunk/users/package/yast2-users.changes Fri Apr 11 15:53:55 2008 @@ -1,4 +1,11 @@ ------------------------------------------------------------------- +Fri Apr 11 15:44:37 CEST 2008 - jsuchome@suse.cz + +- prepare API for "Import User From Previous Installation" in + the 1st stage of installation (fate#302980) +- 2.16.24 + +------------------------------------------------------------------- Fri Apr 11 09:13:47 CEST 2008 - jsuchome@suse.cz - better fix of bnc#373654: read system settings in inst_user Modified: trunk/users/src/UsersSimple.pm URL: http://svn.opensuse.org/viewcvs/yast/trunk/users/src/UsersSimple.pm?rev=4642... ============================================================================== --- trunk/users/src/UsersSimple.pm (original) +++ trunk/users/src/UsersSimple.pm Fri Apr 11 15:53:55 2008 @@ -34,7 +34,10 @@ my $skip_root_dialog = 0; # data of user configured during installation -my %user = (); +#my %user = (); + +# data of users configured during installation +my @users = (); # password encryption method @@ -80,11 +83,18 @@ my $max_length_login = 32; # reason: see for example man utmp, UT_NAMESIZE my $min_length_login = 2; +# see SYSTEM_UID_MAX and SYSTEM_GID_MAX in /etc/login.defs +my $max_system_uid = 499; + +# maps for user data read in 1st stage ('from previous installation') +my %imported_users = (); +my %imported_shadow = (); ##------------------------------------ ##------------------- global imports YaST::YCP::Import ("Directory"); +YaST::YCP::Import ("FileUtils"); YaST::YCP::Import ("ProductControl"); YaST::YCP::Import ("SCR"); YaST::YCP::Import ("UsersUI"); @@ -302,7 +312,18 @@ } sub GetUser { - return %user; + my %ret = (); + %ret = %{$users[0]} if (defined $users[0]); + return %ret; +} + +##------------------------------------ +# Returns the list users configured during installation +# @return the list of user maps +BEGIN { $TYPEINFO{GetUsers} = [ "function", ["list", "any" ]]; } +sub GetUsers { + + return @users; } ##------------------------------------ @@ -317,11 +338,31 @@ my $self = shift; my $data = shift; if (defined $data && (ref ($data) eq "HASH")) { - %user = %{$data}; + my %user = %{$data}; + @users = (); + push @users, %user; } return ""; } +##------------------------------------ +# Saves the user data into the list +# @param list with user data maps (could be empty) +BEGIN { $TYPEINFO{SetUsers} = ["function", + "string", + ["list", "any" ]]; # data to fill in +} +sub SetUsers { + + my $self = shift; + my $data = shift; + if (defined $data && (ref ($data) eq "ARRAY")) { + @users = @{$data}; + } + return ""; +} + + # was root password written in 1st stage? BEGIN { $TYPEINFO{RootPasswordWritten} = ["function", "boolean"];} sub RootPasswordWritten { @@ -589,7 +630,6 @@ my $pw = shift; my $type = shift; my $max_length = $self->GetMaxPasswordLength ($type); -y2internal ("max is $max_length"); my $ret = ""; if (length ($pw) > $max_length) { @@ -815,15 +855,20 @@ BEGIN { $TYPEINFO{Write} = ["function", "boolean"];} sub Write { - my $self = shift; - if (defined $user{"userpassword"}) { - $user{"userpassword"} = $self->CryptPassword ($user{"userpassword"}); - $user{"encrypted"} = YaST::YCP::Integer (1); + my $self = shift; + my $user_defined = 0; + foreach my $user (@users) { + if (defined $user->{"userpassword"}) { + $user->{"userpassword"} = + $self->CryptPassword($user->{"userpassword"}); + $user->{"encrypted"} = YaST::YCP::Integer (1); + } + $user_defined = 1; } my %data = ( "after_auth" => $after_auth, "run_krb_config" => YaST::YCP::Integer ($run_krb_config), - "user" => %user, + "users" => @users, "encryption_method" => $encryption_method, "root_alias" => $root_alias, "autologin_user" => $autologin_user @@ -849,9 +894,10 @@ y2milestone ("enabling step 'root' for second stage"); ProductControl->EnableModule ("root"); } - if ($after_auth ne "users" || %user) { + if ($after_auth ne "users" || $user_defined) { y2milestone ("enabling step 'user' for second stage"); ProductControl->EnableModule ("user"); +# FIXME also when e.g. only encryption was modified } return $ret; @@ -875,15 +921,206 @@ $encryption_method = $data->{"encryption_method"} || $encryption_method; $run_krb_config = bool ($data->{"run_krb_config"}); - if (ref ($data->{"user"}) eq "HASH") { - %user = %{$data->{"user"}}; +# if (ref ($data->{"user"}) eq "HASH") { +# %user = %{$data->{"user"}}; +# } + if (ref ($data->{"users"}) eq "ARRAY") { + @users = @{$data->{"users"}}; } $root_password_written = bool ($data->{"root_password_written"}); $ret = 1; } # SCR->Execute (".target.remove", $file); FIXME not removed due to testing + SCR->Execute (".target.bash", "mv $file $file.bak"); } return bool ($ret); } -1 + +##--------------------------------------------------------------------------- +## functions for handling passwd/shadow files in the 1st stage +## (simplified version of functions from UsersPasswd and Users) + + +# read 'shadow' file from a given directory +# return hash with shadow description +sub read_shadow { + + my $base_directory = shift; + my $file = "$base_directory/shadow"; + my %shadow_tmp = (); + my $in = SCR->Read (".target.string", $file); + + if (! FileUtils->Exists ($file)) { + y2warning ("$file is not available!"); + return undef; + } + if (! defined $in) { + y2warning ("$file cannot be opened for reading!"); + return undef; + } + + foreach my $shadow_entry (split (/\n/,$in)) { + chomp $shadow_entry; + next if ($shadow_entry eq ""); + + my ($uname,$pass,$last_change,$min, $max, $warn, $inact, $expire, $flag) + = split(/:/,$shadow_entry); + my $first = substr ($uname, 0, 1); + + if ($first ne "#" && $first ne "+" && $first ne "-") + { + if (!defined $uname || $uname eq "") { + y2error ("strange line in shadow file: '$shadow_entry'"); + return undef; + } + if (defined $shadow_tmp{$uname}) + { + y2error ("duplicated username in /etc/shadow! Exiting..."); + return undef; + } + $shadow_tmp{$uname} = { + "shadowlastchange" => $last_change, + "shadowwarning" => $warn, + "shadowinactive" => $inact, + "shadowexpire" => $expire, + "shadowmin" => $min, + "shadowmax" => $max, + "shadowflag" => $flag, + "userpassword" => $pass + }; + } + } + return %shadow_tmp; +} + +# read content of 'passwd' file under given directory +# - save data into internal structure +# return boolean (success) +sub read_passwd { + + my $base_directory = shift; + my $shadow_tmp = shift; + my $file = "$base_directory/passwd"; + + %imported_users = (); + %imported_shadow = (); + my %usernames = (); + + if (! FileUtils->Exists ($file)) { + y2warning ("$file is not available!"); + return 0; + } + my $in = SCR->Read (".target.string", $file); + if (! defined $in) { + y2warning ("$file cannot be opened for reading!"); + return 0; + } + + foreach my $user (split (/\n/,$in)) { + chomp $user; + next if ($user eq ""); + + my ($username, $password, $uid, $gid, $full, $home, $shell) + = split(/:/,$user); + my $first = substr ($username, 0, 1); + + if ($first ne "#" && $first ne "+" && $first ne "-") { + + if (!defined $password || !defined $uid || !defined $gid || + !defined $full || !defined $home || !defined $shell || + $username eq "" || $uid eq "" || $gid eq "") { + y2error ("strange line in passwd file: '$user'"); + return 0; + } + + my $user_type = "local"; +# my %grouplist = (); FIXME read group list? + + if (($uid <= $max_system_uid) || ($username eq "nobody")) { + $user_type = "system"; + } + + my $colon = index ($full, ","); + my $additional = ""; + if ( $colon > -1) + { + $additional = $full; + $full = substr ($additional, 0, $colon); + $additional = substr ($additional, $colon + 1, + length ($additional)); + } + + if (defined $usernames{"local"}{$username} || + defined $usernames{"system"}{$username}) + { + y2error ("duplicated username in /etc/passwd! Exiting..."); + return 0; + } + else + { + $usernames{$user_type}{$username} = 1; + } + + # such map we would like to export from the read script... + $imported_users{$user_type}{$username} = { + "addit_data" => $additional, + "cn" => $full, + "homedirectory" => $home, + "uid" => $username, + "uidnumber" => $uid, + "gidnumber" => $gid, + "loginshell" => $shell, + }; + if (defined $shadow_tmp->{$username}) { + # divide shadow map accoring to user type + $imported_shadow{$user_type}{$username} = + $shadow_tmp->{$username}; + } + } + } + return 1; +} + +##------------------------------------ +# Read passwd and shadow files in 1st stage of the installation +# string parameter is path to directory with passwd, shadow files +BEGIN { $TYPEINFO{ReadUserData} = ["function", "boolean", "string"]; } +sub ReadUserData { + + my ($self, $base_directory) = @_; + my $ret = 0; + my $shadow_tmp = read_shadow ($base_directory); + if (defined $shadow_tmp && ref ($shadow_tmp) eq "HASH") { + $ret = read_passwd ($base_directory, $shadow_tmp); + } +# FIXME do not read again for the same directory + return $ret; +} + +##------------------------------------ +# returns hash with imported users of given type +# @param user type +BEGIN { $TYPEINFO{GetImportedUsers} = [ + "function", ["map", "string", "any"], "string"]; +} +sub GetImportedUsers { + + my ($self, $type) = @_; + my %ret = (); + if (defined $imported_users{$type} && ref($imported_users{$type}) eq "HASH") + { + %ret = %{$imported_users{$type}}; + next if (!defined $imported_shadow{$type}); + # add the shadow data into each user map + foreach my $username (keys %ret) { + next if (!defined $imported_shadow{$type}{$username}); + foreach my $key (keys %{$imported_shadow{$type}{$username}}) { + $ret{$username}{$key} = $imported_shadow{$type}{$username}{$key}; + } + } + } + return %ret; +} + +42 # EOF Modified: trunk/users/src/inst_user.ycp URL: http://svn.opensuse.org/viewcvs/yast/trunk/users/src/inst_user.ycp?rev=46421... ============================================================================== --- trunk/users/src/inst_user.ycp (original) +++ trunk/users/src/inst_user.ycp Fri Apr 11 15:53:55 2008 @@ -3,6 +3,7 @@ * * Authors: Klaus Kaempf <kkaempf@suse.de> * Stefan Hundhammer <sh@suse.de> + * Jiri Suchomel <jsuchome@suse.cz> * * Purpose: Start user management module from within installation workflow * @@ -26,22 +27,31 @@ include "users/wizards.ycp"; - symbol ret = `back; + symbol ret = `back; + boolean importing = false; + list<map> users = []; + map<string,any>user = $[]; if (!GetInstArgs::going_back()) { Users::ReadSystemDefaults (false); UsersSimple::Read (); Users::SetEncryptionMethod (UsersSimple::EncryptionMethod ()); - Users::WriteSecurity (); + users = (list<map>) UsersSimple::GetUsers (); + user = UsersSimple::GetUser (); + if (size (users) > 1 || user["__imported"]:nil != nil) + { + Users::SetUsersForImport (users); + importing = true; + } } + // what to call after inst_auth dialog + string client = UsersSimple::AfterAuth(); // check if the user was configured in the 1st stage if (!GetInstArgs::going_back() && - UsersSimple::AfterAuth () == "users" && - UsersSimple::GetUser () != $[]) + client == "users" && user != $[] && !importing) { - map<string,any> user = UsersSimple::GetUser (); y2milestone ("user defined in 1st stage, let's save now..."); boolean progress_orig = Progress::set (false); Users::Read (); @@ -85,13 +95,13 @@ // dialog caption string caption = _("User Authentication Method"); - // help text (shown in the 'busy' situation) - Wizard::SetContents (caption, `Empty (), _("Initialization of module for configuration of authentication..."), - GetInstArgs::enable_back(), GetInstArgs::enable_next() - ); - // what to call after inst_auth dialog - string client = UsersSimple::AfterAuth(); + if (!importing) + Wizard::SetContents (caption, `Empty (), + // help text (shown in the 'busy' situation) + _("Initialization of module for configuration of authentication..."), + GetInstArgs::enable_back(), GetInstArgs::enable_next()); + if (client != "users") { // going back from next step, while kerberos was already configured @@ -183,7 +193,6 @@ user = remove (user, "gidnumber"); } } - string error = Users::AddUser (user); if (error != "") { @@ -196,7 +205,7 @@ } Users::CommitUser (); }); - Users::Write (); + WriteDialog (false); ret = `auto; } else Modified: trunk/users/src/inst_user_first.ycp URL: http://svn.opensuse.org/viewcvs/yast/trunk/users/src/inst_user_first.ycp?rev... ============================================================================== --- trunk/users/src/inst_user_first.ycp (original) +++ trunk/users/src/inst_user_first.ycp Fri Apr 11 15:53:55 2008 @@ -35,7 +35,37 @@ // minimal pw length for CA-management (F#300438) integer pw_min_CA = 4; - + // if importing users from different partition is possible + boolean import_available = false; + string import_dir = Directory::vardir + "/imported/userdata/etc"; + // full info about imported users + map<string,map> imported_users = $[]; + // user names of imported users + list<string> user_names = []; + // names of imported users selected for writing + list<string> to_import = []; + + /* + if (FileUtils::Exists (import_dir + "/passwd") && + FileUtils::Exists (import_dir + "/shadow")) + { + import_available = true; + } + */ + if (!GetInstArgs::going_back() && import_available) + { + if (UsersSimple::ReadUserData (import_dir)) + { + imported_users = (map<string,map>) + UsersSimple::GetImportedUsers ("local"); + user_names = maplist (string name, map u, imported_users,``(name)); + } + if (size (user_names) < 1) + { + y2milestone ("No users to import"); + import_available = false; + } + } // helper function to se package for installation, together with // architecture dependent version void install_package (string package) { @@ -97,6 +127,13 @@ // encryption type "blowfish" : _("Blowfish"), ]; + term import_checkbox = `Left (`CheckBox (`id (`import_ch), + // check box label + _("&Read User Data from a Previous Installation") + )); + + // button label + term import_button = `PushButton (`id(`import), _("&Choose")); term buttons = `VBox (`VSpacing(0.5)); list<string> available_clients = filter (string client, @@ -108,12 +145,25 @@ return Mode::normal () || Package::Available (package); }); available_clients = prepend (available_clients, "users"); + foreach (string client, available_clients, { - buttons = add (buttons, `Left (`RadioButton ( - `id(client), `opt (`notify), button_labels[client]:"") - )); + if (client == "users" && import_available) + buttons = add (buttons, `VBox ( + `Left (`RadioButton ( + `id(client), `opt (`notify), button_labels[client]:"" + )), + `HBox ( + `HSpacing (3), + text_mode ? + `VBox (import_checkbox, `Left (import_button)) : + `HBox (import_checkbox, import_button) + ) + )); + else + buttons = add (buttons, `Left (`RadioButton ( + `id(client), `opt (`notify), button_labels[client]:"") + )); }); -// FIXME 'import previous user' is missing buttons = add (buttons, `VSpacing(0.5)); @@ -241,6 +291,60 @@ } /** + * Helper function: ask user which users to import + */ + list<string> choose_to_import (list<string> all, list<string> selected) { + + list items = maplist (string u, all, ``( + `item (`id (u), u, contains (selected, u)) + )); + boolean all_checked = (size (all) == size (selected)) && size (all) > 0; + integer vsize = size (all) + 3; + if (vsize > 15) vsize = 15; + + UI::OpenDialog (`opt(`decorated), `HBox(`VSpacing (vsize), `VBox ( + `HSpacing(50), + `MultiSelectionBox (`id(`userlist), + // selection box label + _("&Select Users to Read"), items), + `Left (`CheckBox (`id(`all), `opt(`notify), + // check box label + _("Select or Deselect &All"), all_checked + )), + `HBox ( + `PushButton (`id(`ok), `opt(`default), Label::OKButton()), + `PushButton (`id(`cancel), Label::CancelButton()) + ) + ))); + + any ret = nil; + while (true) + { + ret = UI::UserInput (); + if (ret == `all) + { + boolean ch = (boolean)UI::QueryWidget (`id(`all),`Value); + if (ch != all_checked) + { + UI::ChangeWidget (`id(`userlist), `Items, + maplist (string u, all, ``(`item (`id (u), u, ch))) + ); + all_checked = ch; + } + } + if (ret == `ok || ret == `cancel) + break; + } + if (ret == `ok) + { + selected = (list<string>) + UI::QueryWidget(`id(`userlist),`SelectedItems); + } + UI::CloseDialog (); + return ret == `ok ? selected : nil; + } + + /** * Dialog for expert user settings: authentication method as well * as password encryption (see fate 302980) * @return true if user accepted expert settings @@ -273,10 +377,19 @@ foreach (string enc, string l, encoding2label, { UI::ChangeWidget (`id(enc),`Enabled, auth_method == "users"); }); + UI::ChangeWidget (`id (`import_ch), `Value, auth_method == "users" && + size (to_import) > 0); any retval = `cancel; while (true) { retval = UI::UserInput (); + if (retval == `import) + { + list<string> selected = choose_to_import (user_names,to_import); + if (selected != nil) + to_import = selected; + UI::ChangeWidget (`id (`import_ch), `Value, size(to_import)> 0); + } if (is (retval, string) && haskey (button_labels, (string)retval)) { UI::ChangeWidget (`id(`krb),`Enabled, @@ -284,6 +397,21 @@ foreach (string enc, string l, encoding2label, { UI::ChangeWidget (`id(enc),`Enabled, retval == "users"); }); + UI::ChangeWidget (`id (`import_ch), `Enabled, retval== "users"); + UI::ChangeWidget (`id (`import), `Enabled, retval == "users"); + } + if (retval == `accept && import_available && to_import == [] && + UI::QueryWidget (`id(`import_ch), `Value) == true) + { + // force selecting when only checkbox is checked + list<string> selected = choose_to_import (user_names,to_import); + if (selected != nil) + to_import = selected; + else + { + retval = `notnext; + continue; + } } if (retval == `cancel || retval == `accept || retval == `back) break; @@ -299,6 +427,9 @@ use_kerberos = false; else use_kerberos = (boolean) UI::QueryWidget (`id(`krb), `Value); + if (auth_method != "users" || + UI::QueryWidget (`id (`import_ch), `Value) == false) + to_import = []; } Wizard::CloseDialog (); return (retval == `accept); @@ -311,6 +442,7 @@ // summary label string details_line =sformat(_("The password encryption method is %1."), encoding2label[encryption_method]:encryption_method); + term imported_term = `Empty (); if (auth_method != "users") { // summary line: %1 is LDAP/NIS etc. @@ -322,10 +454,25 @@ // // summary label details_line = _("The configuration will be available later during the installation."); } + else if (to_import != []) + { + // summary label, %1 are user names (comma separated) + string imported = sformat (_("Users %1 will be imported."), + mergestring (to_import, ",")); + if (size (to_import) == 1) + // summary label, %1 is user name + imported = sformat (_("User %1 will be imported."), + to_import[0]:""); + if (text_mode) + auth_line = auth_line + "<br>" + imported; + else + imported_term = `Left (`Label (imported)); + } term status = text_mode ? `RichText (auth_line + "<br>" + details_line) : `VBox ( `Left (`Label (auth_line)), + imported_term, `Left (`Label (details_line)) ); term button = `HBox ( @@ -344,7 +491,19 @@ ))); } - map<string,any>user = UsersSimple::GetUser (); + list<map> users = (list<map>) UsersSimple::GetUsers (); + map<string,any>user = $[]; + if (size (users) > 1) + { + to_import = maplist (map u, users, ``(u["uid"]:"")); + } + if (size (users) == 1) + { + if (users[0,"__imported"]:nil != nil) + to_import = [ users[0,"uid"]:"" ]; + else + user = (map<string,any>) users[0]:$[]; + } string user_type = user["type"]:"local"; string username = user["uid"]:""; @@ -373,12 +532,20 @@ (username != "" && UsersSimple::GetRootAlias () == username); term fields = `VBox ( + /* `InputField (`id (`cn), `opt (`notify, `hstretch), // text entry _("User's &Full Name"), cn), `InputField (`id (`username), `opt (`notify, `hstretch), // input field for login name _("&Username"),username), + */ + `TextEntry (`id (`cn), `opt (`notify, `hstretch), + // text entry + _("User's &Full Name"), cn), + `TextEntry (`id (`username), `opt (`notify, `hstretch), + // input field for login name + _("&Username"),username), `Password (`id (`pw1), `opt (`hstretch), Label::Password(), password == nil ? "" : password), `Password (`id (`pw2), `opt (`hstretch), Label::ConfirmPassword(), @@ -415,7 +582,7 @@ // dialog caption Wizard::SetContents (_("Create New User"), contents, main_help (), - GetInstArgs::enable_back(), GetInstArgs::enable_next() + GetInstArgs::enable_back(), GetInstArgs::enable_next() || Mode::normal() ); foreach (symbol w, [`cn, `username, `pw1, `pw2, `root_pw, `root_mail, `autologin ], { @@ -438,7 +605,8 @@ UI::ReplaceWidget (`id (`rp_status), get_status_term ()); foreach (symbol w, [`cn, `username, `pw1, `pw2, `root_pw, `root_mail, `autologin ], { - UI::ChangeWidget (`id (w), `Enabled,auth_method == "users"); + UI::ChangeWidget (`id (w), `Enabled, + auth_method == "users" && to_import == []); }); Wizard::RestoreHelp (main_help ()); } @@ -580,7 +748,21 @@ { UsersSimple::SetAfterAuth (auth_method); UsersSimple::SetKerberosConfiguration (use_kerberos); - if (auth_method == "users" && username != "") + if (auth_method == "users" && to_import != []) + { + list create_users = []; + foreach (string name, to_import, { + map u = imported_users[name]:$[]; + u["__imported"] = true; + create_users = add (create_users, u); + }); + UsersSimple::SetUsers (create_users); + UsersSimple::SkipRootPasswordDialog (false); + UsersSimple::SetRootPassword (""); + UsersSimple::SetAutologinUser (""); + UsersSimple::SetRootAlias (""); + } + else if (auth_method == "users" && username != "") { // save the first user data map<string,any> user_map = $[ @@ -588,7 +770,7 @@ "userpassword" : password, "cn" : cn ]; - UsersSimple::SetUser (user_map); + UsersSimple::SetUsers ([user_map]); UsersSimple::SkipRootPasswordDialog (root_pw); UsersSimple::SetRootPassword (root_pw ? password : ""); UsersSimple::SetAutologinUser ( @@ -628,7 +810,7 @@ UsersSimple::SetAutologinUser (""); UsersSimple::SetRootAlias (""); UsersSimple::SetRootPassword (""); - UsersSimple::SetUser ($[]); + UsersSimple::SetUsers ([]); } -- To unsubscribe, e-mail: yast-commit+unsubscribe@opensuse.org For additional commands, e-mail: yast-commit+help@opensuse.org