2016-07-27 04:05:47 +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/>.
|
|
|
|
|
2016-09-27 08:42:50 +00:00
|
|
|
import json
|
2016-11-09 01:57:35 +00:00
|
|
|
import logging
|
2016-11-09 22:59:39 +00:00
|
|
|
import os
|
2020-07-03 03:35:32 +00:00
|
|
|
import pwd
|
2018-06-08 19:21:02 +00:00
|
|
|
import shlex
|
2016-11-09 22:59:39 +00:00
|
|
|
import sys
|
2016-07-27 04:05:47 +00:00
|
|
|
|
2020-04-15 23:22:52 +00:00
|
|
|
from console_conf.ui.views import IdentityView, LoginView
|
2020-07-03 03:35:32 +00:00
|
|
|
from subiquitycore.snapd import SnapdConnection
|
2020-04-15 23:22:52 +00:00
|
|
|
from subiquitycore.ssh import get_ips_standalone, host_key_info
|
2020-07-31 00:14:51 +00:00
|
|
|
from subiquitycore.tuicontroller import TuiController
|
2017-01-25 22:18:03 +00:00
|
|
|
from subiquitycore.utils import disable_console_conf, run_command
|
2016-07-27 04:05:47 +00:00
|
|
|
|
2016-11-09 01:57:35 +00:00
|
|
|
log = logging.getLogger("console_conf.controllers.identity")
|
|
|
|
|
2018-05-24 19:09:36 +00:00
|
|
|
|
2018-06-08 19:21:02 +00:00
|
|
|
def get_core_version():
|
|
|
|
"""For a ubuntu-core system, return its version or None"""
|
|
|
|
|
|
|
|
path = "/usr/lib/os-release"
|
|
|
|
try:
|
|
|
|
with open(path, "r") as fp:
|
|
|
|
content = fp.read()
|
|
|
|
except FileNotFoundError:
|
|
|
|
return None
|
|
|
|
|
|
|
|
version = None
|
|
|
|
for line in shlex.split(content):
|
|
|
|
key, _, value = line.partition("=")
|
2018-06-12 15:14:37 +00:00
|
|
|
if key == "ID" and value != "ubuntu-core":
|
2018-06-08 19:21:02 +00:00
|
|
|
return None
|
|
|
|
if key == "VERSION_ID":
|
|
|
|
version = value
|
|
|
|
break
|
|
|
|
|
|
|
|
return version
|
|
|
|
|
|
|
|
|
2020-07-03 03:35:32 +00:00
|
|
|
def get_managed():
|
|
|
|
"""Check if device is managed"""
|
|
|
|
con = SnapdConnection("", "/run/snapd.socket")
|
|
|
|
return con.get("v2/system-info").json()["result"]["managed"]
|
|
|
|
|
2016-11-09 22:59:39 +00:00
|
|
|
|
2020-07-03 03:35:32 +00:00
|
|
|
def get_realname(username):
|
2016-11-09 22:59:39 +00:00
|
|
|
try:
|
2020-07-03 03:35:32 +00:00
|
|
|
info = pwd.getpwnam(username)
|
|
|
|
except KeyError:
|
|
|
|
return ""
|
|
|
|
return info.pw_gecos.split(",", 1)[0]
|
|
|
|
|
|
|
|
|
|
|
|
def get_device_owner():
|
|
|
|
"""Get device owner, if any"""
|
|
|
|
con = SnapdConnection("", "/run/snapd.socket")
|
|
|
|
for user in con.get("v2/users").json()["result"]:
|
2020-07-08 22:47:46 +00:00
|
|
|
if "username" not in user:
|
|
|
|
continue
|
|
|
|
username = user["username"]
|
|
|
|
homedir = "/home/" + username
|
|
|
|
if os.path.isdir(homedir):
|
2020-07-03 03:35:32 +00:00
|
|
|
return {
|
2020-07-09 11:22:20 +00:00
|
|
|
"username": username,
|
|
|
|
"realname": get_realname(username),
|
2020-07-08 22:47:46 +00:00
|
|
|
"homedir": homedir,
|
2016-11-09 22:59:39 +00:00
|
|
|
}
|
|
|
|
return None
|
|
|
|
|
2018-05-24 19:09:36 +00:00
|
|
|
|
2017-02-16 22:29:56 +00:00
|
|
|
login_details_tmpl = """\
|
2018-06-08 19:21:02 +00:00
|
|
|
Ubuntu Core {version} on {first_ip} ({tty_name})
|
2017-02-16 22:29:56 +00:00
|
|
|
{host_key_info}
|
2020-04-06 02:10:26 +00:00
|
|
|
|
2017-02-16 22:29:56 +00:00
|
|
|
To login:
|
|
|
|
{sshcommands}
|
|
|
|
Personalize your account at https://login.ubuntu.com.
|
2016-11-22 21:16:12 +00:00
|
|
|
"""
|
2016-11-09 22:59:39 +00:00
|
|
|
|
2018-05-24 19:09:36 +00:00
|
|
|
|
2017-03-28 02:54:50 +00:00
|
|
|
login_details_tmpl_no_ip = """\
|
2018-06-08 19:21:02 +00:00
|
|
|
Ubuntu Core {version} on <no ip address> ({tty_name})
|
2017-03-28 02:54:50 +00:00
|
|
|
|
|
|
|
You cannot log in until the system has an IP address. (Is there
|
|
|
|
supposed to be a DHCP server running on your network?)
|
|
|
|
|
|
|
|
Personalize your account at https://login.ubuntu.com.
|
|
|
|
"""
|
|
|
|
|
2018-05-24 19:09:36 +00:00
|
|
|
|
2017-02-16 22:29:56 +00:00
|
|
|
def write_login_details(fp, username, ips):
|
|
|
|
sshcommands = "\n"
|
2016-11-09 22:59:39 +00:00
|
|
|
for ip in ips:
|
2018-05-24 19:09:36 +00:00
|
|
|
sshcommands += " ssh %s@%s\n" % (username, ip)
|
|
|
|
tty_name = os.ttyname(0)[5:] # strip off the /dev/
|
2018-06-08 19:21:02 +00:00
|
|
|
version = get_core_version() or "16"
|
2017-03-28 02:54:50 +00:00
|
|
|
if len(ips) == 0:
|
|
|
|
fp.write(
|
|
|
|
login_details_tmpl_no_ip.format(
|
2018-06-08 19:21:02 +00:00
|
|
|
sshcommands=sshcommands, tty_name=tty_name, version=version
|
|
|
|
)
|
2023-07-25 21:26:25 +00:00
|
|
|
)
|
2017-03-28 02:54:50 +00:00
|
|
|
else:
|
|
|
|
first_ip = ips[0]
|
2018-05-24 19:09:36 +00:00
|
|
|
fp.write(
|
|
|
|
login_details_tmpl.format(
|
|
|
|
sshcommands=sshcommands,
|
|
|
|
host_key_info=host_key_info(),
|
|
|
|
tty_name=tty_name,
|
2018-06-08 19:21:02 +00:00
|
|
|
first_ip=first_ip,
|
2018-06-11 11:22:39 +00:00
|
|
|
version=version,
|
|
|
|
)
|
2023-07-25 21:26:25 +00:00
|
|
|
)
|
2018-05-24 19:09:36 +00:00
|
|
|
|
2020-04-15 23:08:56 +00:00
|
|
|
|
|
|
|
def write_login_details_standalone():
|
|
|
|
owner = get_device_owner()
|
|
|
|
ips = get_ips_standalone()
|
2017-03-28 02:54:50 +00:00
|
|
|
if len(ips) == 0:
|
2020-07-03 03:35:32 +00:00
|
|
|
if owner is None:
|
|
|
|
print("device managed without user")
|
|
|
|
return 2
|
|
|
|
else:
|
|
|
|
tty_name = os.ttyname(0)[5:]
|
|
|
|
version = get_core_version() or "16"
|
|
|
|
print(login_details_tmpl_no_ip.format(tty_name=tty_name, version=version))
|
|
|
|
return 2
|
|
|
|
if owner is None:
|
|
|
|
print("device managed without user @ {}".format(", ".join(ips)))
|
|
|
|
else:
|
|
|
|
write_login_details(sys.stdout, owner["username"], ips)
|
2016-11-09 22:59:39 +00:00
|
|
|
return 0
|
|
|
|
|
|
|
|
|
2020-07-31 00:14:51 +00:00
|
|
|
class IdentityController(TuiController):
|
2019-08-06 02:11:57 +00:00
|
|
|
def __init__(self, app):
|
|
|
|
super().__init__(app)
|
2019-08-06 02:41:58 +00:00
|
|
|
self.model = app.base_model.identity
|
2016-07-27 10:15:22 +00:00
|
|
|
|
2020-09-17 23:44:00 +00:00
|
|
|
def make_ui(self):
|
2020-07-03 03:35:32 +00:00
|
|
|
if get_managed():
|
|
|
|
device_owner = get_device_owner()
|
|
|
|
if device_owner:
|
|
|
|
self.model.add_user(device_owner)
|
2020-09-17 23:44:00 +00:00
|
|
|
return self.make_login_view()
|
|
|
|
else:
|
|
|
|
return IdentityView(self.model, self)
|
2016-09-29 19:17:15 +00:00
|
|
|
|
2016-09-27 08:42:50 +00:00
|
|
|
def identity_done(self, email):
|
2016-09-29 19:17:15 +00:00
|
|
|
if self.opts.dry_run:
|
|
|
|
result = {
|
|
|
|
"realname": email,
|
|
|
|
"username": email,
|
|
|
|
}
|
|
|
|
self.model.add_user(result)
|
2022-01-25 21:10:40 +00:00
|
|
|
login_details_path = self.opts.output_base + "/login-details.txt"
|
2016-09-29 19:17:15 +00:00
|
|
|
else:
|
2019-12-19 23:04:38 +00:00
|
|
|
self.app.urwid_loop.draw_screen()
|
2018-05-24 19:09:36 +00:00
|
|
|
cp = run_command(["snap", "create-user", "--sudoer", "--json", email])
|
2018-05-18 01:11:15 +00:00
|
|
|
if cp.returncode != 0:
|
2020-05-18 10:57:41 +00:00
|
|
|
if isinstance(self.ui.body, IdentityView):
|
|
|
|
self.ui.body.snap_create_user_failed(
|
|
|
|
"Creating user failed:", cp.stderr
|
|
|
|
)
|
2016-09-27 08:42:50 +00:00
|
|
|
return
|
|
|
|
else:
|
2018-05-18 01:11:15 +00:00
|
|
|
data = json.loads(cp.stdout)
|
2016-09-27 08:42:50 +00:00
|
|
|
result = {
|
|
|
|
"realname": email,
|
|
|
|
"username": data["username"],
|
|
|
|
}
|
2016-11-09 22:59:39 +00:00
|
|
|
os.makedirs("/run/console-conf", exist_ok=True)
|
|
|
|
login_details_path = "/run/console-conf/login-details.txt"
|
2016-09-27 08:42:50 +00:00
|
|
|
self.model.add_user(result)
|
2016-11-09 22:59:39 +00:00
|
|
|
ips = []
|
2019-11-18 00:55:43 +00:00
|
|
|
net_model = self.app.base_model.network
|
2016-11-09 22:59:39 +00:00
|
|
|
for dev in net_model.get_all_netdevs():
|
2017-02-16 23:38:14 +00:00
|
|
|
ips.extend(dev.actual_global_ip_addresses)
|
2016-11-09 01:57:35 +00:00
|
|
|
with open(login_details_path, "w") as fp:
|
2017-02-16 22:29:56 +00:00
|
|
|
write_login_details(fp, result["username"], ips)
|
2016-10-10 23:48:28 +00:00
|
|
|
self.login()
|
2016-09-27 08:42:50 +00:00
|
|
|
|
2016-11-01 23:44:04 +00:00
|
|
|
def cancel(self):
|
|
|
|
# You can only go back if we haven't created a user yet.
|
|
|
|
if self.model.user is None:
|
2019-12-16 10:02:03 +00:00
|
|
|
self.app.prev_screen()
|
2016-11-01 23:44:04 +00:00
|
|
|
|
2020-09-17 23:44:00 +00:00
|
|
|
def make_login_view(self):
|
2016-07-27 22:30:27 +00:00
|
|
|
title = "Configuration Complete"
|
|
|
|
self.ui.set_header(title)
|
|
|
|
|
2019-11-18 00:55:43 +00:00
|
|
|
net_model = self.app.base_model.network
|
2016-11-07 00:35:42 +00:00
|
|
|
ifaces = net_model.get_all_netdevs()
|
|
|
|
login_view = LoginView(self.opts, self.model, self, ifaces)
|
2020-04-09 11:12:34 +00:00
|
|
|
login_view._w.focus_position = 2
|
2020-11-05 20:54:30 +00:00
|
|
|
return login_view
|
2016-07-27 22:30:27 +00:00
|
|
|
|
2020-09-17 23:44:00 +00:00
|
|
|
def login(self):
|
|
|
|
self.ui.set_body(self.make_login_view())
|
2016-09-27 02:33:54 +00:00
|
|
|
|
|
|
|
def login_done(self):
|
2016-09-27 08:42:50 +00:00
|
|
|
if not self.opts.dry_run:
|
2018-05-24 19:09:36 +00:00
|
|
|
# stop the console-conf services (this will kill the
|
|
|
|
# current process).
|
2017-01-25 22:18:03 +00:00
|
|
|
disable_console_conf()
|
2016-09-27 08:42:50 +00:00
|
|
|
|
2019-12-16 10:05:35 +00:00
|
|
|
self.app.exit()
|