2015-08-18 18:34:59 +00:00
|
|
|
# Copyright 2015 Canonical, Ltd.
|
|
|
|
#
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU Affero General Public License as
|
|
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
|
|
# License, or (at your option) any later version.
|
|
|
|
#
|
|
|
|
# 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 Affero General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
""" Welcome
|
|
|
|
|
|
|
|
Welcome provides user with language selection
|
|
|
|
|
|
|
|
"""
|
|
|
|
import logging
|
2015-09-02 19:21:14 +00:00
|
|
|
from urwid import (Pile, Columns, Text, ListBox)
|
2016-06-30 18:17:01 +00:00
|
|
|
from subiquitycore.ui.buttons import done_btn, cancel_btn
|
|
|
|
from subiquitycore.ui.interactive import (PasswordEditor,
|
|
|
|
RealnameEditor,
|
|
|
|
StringEditor,
|
|
|
|
UsernameEditor)
|
|
|
|
from subiquitycore.ui.utils import Padding, Color
|
2016-07-26 02:21:24 +00:00
|
|
|
from subiquitycore.view import BaseView
|
2016-06-30 18:17:01 +00:00
|
|
|
from subiquitycore.curtin import (curtin_write_postinst_config,
|
|
|
|
curtin_configure_user)
|
|
|
|
|
|
|
|
|
|
|
|
log = logging.getLogger("subiquitycore.views.identity")
|
2015-08-18 18:34:59 +00:00
|
|
|
|
2015-11-20 22:00:10 +00:00
|
|
|
HOSTNAME_MAXLEN = 64
|
2015-11-03 20:00:05 +00:00
|
|
|
REALNAME_MAXLEN = 160
|
2015-11-20 22:00:10 +00:00
|
|
|
SSH_IMPORT_MAXLEN = 256 + 3 # account for lp: or gh:
|
|
|
|
USERNAME_MAXLEN = 32
|
2015-11-03 20:00:05 +00:00
|
|
|
|
2015-08-18 18:34:59 +00:00
|
|
|
|
2016-07-26 02:21:24 +00:00
|
|
|
class IdentityView(BaseView):
|
2016-06-22 19:19:54 +00:00
|
|
|
def __init__(self, model, signal, opts):
|
2015-08-18 18:34:59 +00:00
|
|
|
self.model = model
|
|
|
|
self.signal = signal
|
2016-06-22 19:19:54 +00:00
|
|
|
self.opts = opts
|
2015-08-18 18:34:59 +00:00
|
|
|
self.items = []
|
2015-11-03 20:00:05 +00:00
|
|
|
self.realname = RealnameEditor(caption="")
|
2015-11-20 22:05:23 +00:00
|
|
|
self.hostname = UsernameEditor(caption="")
|
Implement username field protection
man 8 useradd says:
It is usually recommended to only use usernames that begin with a
lower case letter or an underscore, followed by lower case
letters, digits, underscores, or dashes. They can end with a
dollar sign. In regular expression terms: [a-z_][a-z0-9_-]*[$]?
On Debian, the only constraints are that usernames must neither
start with a dash ('-') nor plus ('+') nor tilde ('~') nor
contain a colon (':'), a comma (','), or a whitespace (space: ' ',
end of line: '\n', tabulation: '\t', etc.). Note that using a
slash ('/') may break the default algorithm for the definition
of the user's home directory.
Usernames may only be up to 32 characters long.
In this patch we implement most of this. Subset of the regular
expression suggested above is used to limit input into the username
field. We've not yet determined how to provide a fixed width widget
so at this time, user can input more than 32 characters, but upon
selectin done, we raise and error and reset the state.
Signed-off-by: Ryan Harper <ryan.harper@canonical.com>
2015-10-23 20:21:13 +00:00
|
|
|
self.username = UsernameEditor(caption="")
|
2015-08-27 14:28:48 +00:00
|
|
|
self.password = PasswordEditor(caption="")
|
2015-11-20 22:00:10 +00:00
|
|
|
self.ssh_import_id = StringEditor(caption="")
|
2015-11-20 22:05:23 +00:00
|
|
|
self.ssh_import_confirmed = True
|
2015-09-28 15:03:01 +00:00
|
|
|
self.error = Text("", align="center")
|
2015-08-27 14:28:48 +00:00
|
|
|
self.confirm_password = PasswordEditor(caption="")
|
2015-08-18 18:34:59 +00:00
|
|
|
|
|
|
|
body = [
|
2015-11-20 22:00:10 +00:00
|
|
|
Padding.center_90(self._build_model_inputs()),
|
2015-08-18 18:34:59 +00:00
|
|
|
Padding.line_break(""),
|
2015-11-20 22:00:10 +00:00
|
|
|
Padding.center_90(Color.info_error(self.error)),
|
2015-09-28 15:03:01 +00:00
|
|
|
Padding.line_break(""),
|
2015-11-19 21:24:40 +00:00
|
|
|
Padding.fixed_10(self._build_buttons()),
|
2015-08-18 18:34:59 +00:00
|
|
|
]
|
2015-08-27 14:28:48 +00:00
|
|
|
super().__init__(ListBox(body))
|
2015-08-18 18:34:59 +00:00
|
|
|
|
|
|
|
def _build_buttons(self):
|
|
|
|
cancel = cancel_btn(on_press=self.cancel)
|
|
|
|
done = done_btn(on_press=self.done)
|
|
|
|
|
|
|
|
buttons = [
|
2015-08-24 15:51:10 +00:00
|
|
|
Color.button(done, focus_map='button focus'),
|
|
|
|
Color.button(cancel, focus_map='button focus')
|
2015-08-18 18:34:59 +00:00
|
|
|
]
|
|
|
|
return Pile(buttons)
|
|
|
|
|
|
|
|
def _build_model_inputs(self):
|
|
|
|
sl = [
|
2015-09-11 18:12:26 +00:00
|
|
|
Columns(
|
|
|
|
[
|
2015-11-20 22:00:10 +00:00
|
|
|
("weight", 0.2, Text("Your name:", align="right")),
|
2015-09-11 18:12:26 +00:00
|
|
|
("weight", 0.3,
|
|
|
|
Color.string_input(self.realname,
|
|
|
|
focus_map="string_input focus"))
|
|
|
|
],
|
|
|
|
dividechars=4
|
|
|
|
),
|
2016-06-23 20:27:09 +00:00
|
|
|
Columns(
|
|
|
|
[
|
|
|
|
("weight", 0.2, Text("Your server's name:",
|
|
|
|
align="right")),
|
|
|
|
("weight", 0.3,
|
|
|
|
Color.string_input(self.hostname,
|
|
|
|
focus_map="string_input focus"))
|
|
|
|
],
|
|
|
|
dividechars=4
|
|
|
|
),
|
|
|
|
Columns(
|
|
|
|
[
|
|
|
|
("weight", 0.2, Text("", align="right")),
|
|
|
|
("weight", 0.3, Color.info_minor(
|
|
|
|
Text("The name it uses when it talks to "
|
|
|
|
"other computers", align="left"))),
|
|
|
|
],
|
|
|
|
dividechars=4
|
|
|
|
),
|
2015-11-20 22:00:10 +00:00
|
|
|
Columns(
|
|
|
|
[
|
|
|
|
("weight", 0.2, Text("Pick a username:", align="right")),
|
2015-08-27 14:28:48 +00:00
|
|
|
("weight", 0.3,
|
|
|
|
Color.string_input(self.username,
|
|
|
|
focus_map="string_input focus"))
|
|
|
|
],
|
|
|
|
dividechars=4
|
|
|
|
),
|
|
|
|
Columns(
|
|
|
|
[
|
2015-11-20 22:00:10 +00:00
|
|
|
("weight", 0.2, Text("Choose a password:", align="right")),
|
2015-08-27 14:28:48 +00:00
|
|
|
("weight", 0.3,
|
|
|
|
Color.string_input(self.password,
|
|
|
|
focus_map="string_input focus"))
|
|
|
|
],
|
|
|
|
dividechars=4
|
|
|
|
),
|
|
|
|
Columns(
|
|
|
|
[
|
2015-11-20 22:05:23 +00:00
|
|
|
("weight", 0.2, Text("Confirm your password:",
|
2015-11-20 22:00:10 +00:00
|
|
|
align="right")),
|
2015-08-27 14:28:48 +00:00
|
|
|
("weight", 0.3,
|
|
|
|
Color.string_input(self.confirm_password,
|
|
|
|
focus_map="string_input focus"))
|
|
|
|
],
|
|
|
|
dividechars=4
|
2015-11-20 22:00:10 +00:00
|
|
|
),
|
|
|
|
Columns(
|
|
|
|
[
|
|
|
|
("weight", 0.2, Text("Import SSH identity:",
|
|
|
|
align="right")),
|
|
|
|
("weight", 0.3,
|
|
|
|
Color.string_input(self.ssh_import_id,
|
|
|
|
focus_map="string_input focus"))
|
|
|
|
],
|
|
|
|
dividechars=4
|
|
|
|
),
|
|
|
|
|
|
|
|
Columns(
|
|
|
|
[
|
|
|
|
("weight", 0.2, Text("", align="right")),
|
|
|
|
("weight", 0.3, Color.info_minor(
|
|
|
|
Text("Input your SSH user id from "
|
2016-06-22 19:19:54 +00:00
|
|
|
"Ubuntu SSO (sso:email), "
|
2015-11-20 22:00:10 +00:00
|
|
|
"Launchpad (lp:username) or "
|
|
|
|
"Github (gh:username).",
|
|
|
|
align="left"))),
|
|
|
|
],
|
|
|
|
dividechars=4
|
|
|
|
),
|
2015-08-18 18:34:59 +00:00
|
|
|
]
|
|
|
|
return Pile(sl)
|
|
|
|
|
|
|
|
def done(self, result):
|
2015-11-20 22:00:10 +00:00
|
|
|
# check in display order:
|
|
|
|
# realname, hostname, username, password, ssh
|
2015-11-03 20:00:05 +00:00
|
|
|
if len(self.realname.value) < 1:
|
|
|
|
self.error.set_text("Realname missing.")
|
|
|
|
self.realname.value = ""
|
|
|
|
return
|
|
|
|
|
|
|
|
if len(self.realname.value) > REALNAME_MAXLEN:
|
|
|
|
self.error.set_text("Realname too long, must be < " +
|
|
|
|
str(REALNAME_MAXLEN))
|
|
|
|
self.realname.value = ""
|
|
|
|
return
|
|
|
|
|
2016-06-23 20:27:09 +00:00
|
|
|
if len(self.hostname.value) < 1:
|
|
|
|
self.error.set_text("Server name missing.")
|
|
|
|
self.hostname.value = ""
|
|
|
|
return
|
|
|
|
|
|
|
|
if len(self.hostname.value) > HOSTNAME_MAXLEN:
|
|
|
|
self.error.set_text("Server name too long, must be < " +
|
|
|
|
str(HOSTNAME_MAXLEN))
|
|
|
|
self.hostname.value = ""
|
|
|
|
return
|
|
|
|
|
2015-11-03 20:00:05 +00:00
|
|
|
if len(self.username.value) < 1:
|
|
|
|
self.error.set_text("Username missing.")
|
|
|
|
self.username.value = ""
|
|
|
|
return
|
|
|
|
|
|
|
|
if len(self.username.value) > USERNAME_MAXLEN:
|
|
|
|
self.error.set_text("Username too long, must be < " +
|
|
|
|
str(USERNAME_MAXLEN))
|
Implement username field protection
man 8 useradd says:
It is usually recommended to only use usernames that begin with a
lower case letter or an underscore, followed by lower case
letters, digits, underscores, or dashes. They can end with a
dollar sign. In regular expression terms: [a-z_][a-z0-9_-]*[$]?
On Debian, the only constraints are that usernames must neither
start with a dash ('-') nor plus ('+') nor tilde ('~') nor
contain a colon (':'), a comma (','), or a whitespace (space: ' ',
end of line: '\n', tabulation: '\t', etc.). Note that using a
slash ('/') may break the default algorithm for the definition
of the user's home directory.
Usernames may only be up to 32 characters long.
In this patch we implement most of this. Subset of the regular
expression suggested above is used to limit input into the username
field. We've not yet determined how to provide a fixed width widget
so at this time, user can input more than 32 characters, but upon
selectin done, we raise and error and reset the state.
Signed-off-by: Ryan Harper <ryan.harper@canonical.com>
2015-10-23 20:21:13 +00:00
|
|
|
self.username.value = ""
|
|
|
|
return
|
|
|
|
|
2015-11-20 22:00:10 +00:00
|
|
|
if len(self.password.value) < 1:
|
|
|
|
self.error.set_text("Password must be set")
|
|
|
|
self.password.value = ""
|
|
|
|
self.confirm_password.value = ""
|
|
|
|
return
|
|
|
|
|
|
|
|
if self.password.value != self.confirm_password.value:
|
|
|
|
self.error.set_text("Passwords do not match.")
|
|
|
|
self.password.value = ""
|
|
|
|
self.confirm_password.value = ""
|
|
|
|
return
|
|
|
|
|
|
|
|
# ssh_id is optional
|
|
|
|
if len(self.ssh_import_id.value) > SSH_IMPORT_MAXLEN:
|
|
|
|
self.error.set_text("SSH id too long, must be < " +
|
|
|
|
str(SSH_IMPORT_MAXLEN))
|
|
|
|
self.ssh_import_id.value = ""
|
|
|
|
return
|
|
|
|
|
2015-09-21 21:40:26 +00:00
|
|
|
cpassword = self.model.encrypt_password(self.password.value)
|
|
|
|
log.debug("*crypted* User input: {} {} {}".format(
|
|
|
|
self.username.value, cpassword, cpassword))
|
2015-08-18 18:34:59 +00:00
|
|
|
result = {
|
2016-06-23 20:27:09 +00:00
|
|
|
"hostname": self.hostname.value,
|
2015-09-11 18:12:26 +00:00
|
|
|
"realname": self.realname.value,
|
2015-08-18 18:34:59 +00:00
|
|
|
"username": self.username.value,
|
2015-09-21 21:40:26 +00:00
|
|
|
"password": cpassword,
|
|
|
|
"confirm_password": cpassword,
|
2015-08-18 18:34:59 +00:00
|
|
|
}
|
2015-11-20 22:00:10 +00:00
|
|
|
|
|
|
|
# if user specifed a value, allow user to validate fingerprint
|
|
|
|
if self.ssh_import_id.value:
|
|
|
|
if self.ssh_import_confirmed is True:
|
|
|
|
result.update({'ssh_import_id': self.ssh_import_id.value})
|
|
|
|
else:
|
|
|
|
self.emit_signal('identity:confirm-ssh-id',
|
|
|
|
self.ssh_import_id.value)
|
|
|
|
return
|
|
|
|
|
2015-08-18 18:34:59 +00:00
|
|
|
log.debug("User input: {}".format(result))
|
2016-06-22 19:19:54 +00:00
|
|
|
self.model.add_user(result)
|
|
|
|
|
2015-11-16 20:59:59 +00:00
|
|
|
try:
|
|
|
|
curtin_write_postinst_config(result)
|
2016-06-22 19:19:54 +00:00
|
|
|
curtin_configure_user(result, dryrun=self.opts.dry_run)
|
2015-11-16 20:59:59 +00:00
|
|
|
except PermissionError:
|
|
|
|
log.exception('Failed to write curtin post-install config')
|
|
|
|
self.signal.emit_signal('filesystem:error',
|
2016-06-22 19:19:54 +00:00
|
|
|
'curtin_write_postinst_config', result)
|
2015-11-16 20:59:59 +00:00
|
|
|
return None
|
|
|
|
|
|
|
|
self.signal.emit_signal('installprogress:wrote-postinstall')
|
2016-06-28 15:28:56 +00:00
|
|
|
# show next view
|
|
|
|
if self.opts.firstboot:
|
|
|
|
self.signal.emit_signal('menu:identity:login:main')
|
|
|
|
else:
|
|
|
|
self.signal.emit_signal('menu:installprogress:main')
|
|
|
|
|
2015-08-18 18:34:59 +00:00
|
|
|
def cancel(self, button):
|
2015-10-29 20:58:51 +00:00
|
|
|
self.signal.prev_signal()
|