From ea7ccdca81341f3118258908b2475c24b3fd058d Mon Sep 17 00:00:00 2001 From: Tristan Ross Date: Thu, 2 May 2024 15:57:00 -0700 Subject: [PATCH] feat: use sessions to log in and out --- lib/logic/route_args.dart | 22 +++++++++++++ lib/main.dart | 20 ++++++------ lib/views/desktop.dart | 9 ++++++ lib/views/login.dart | 9 ++++-- lib/widgets/login.dart | 3 ++ lib/widgets/power.dart | 2 +- linux/application-priv.h | 4 +-- linux/application.cc | 10 ++---- linux/channels/auth.cc | 68 ++++++++++++++++++++++++++++++++++++--- linux/channels/auth.h | 8 ++++- linux/data/pam | 5 ++- nix/module.nix | 6 +++- 12 files changed, 134 insertions(+), 32 deletions(-) create mode 100644 lib/logic/route_args.dart diff --git a/lib/logic/route_args.dart b/lib/logic/route_args.dart new file mode 100644 index 00000000..e165519d --- /dev/null +++ b/lib/logic/route_args.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +class AuthedRouteArguments { + const AuthedRouteArguments({ + this.userName = null, + this.isSession = false, + }); + + final String? userName; + final bool isSession; + + static AuthedRouteArguments? maybeOf(BuildContext context) { + final route = ModalRoute.of(context); + if (route == null) return null; + if (route.settings.arguments == null) return null; + return route.settings.arguments as AuthedRouteArguments; + } + + static AuthedRouteArguments of(BuildContext context) { + return maybeOf(context) ?? AuthedRouteArguments(); + } +} diff --git a/lib/main.dart b/lib/main.dart index 185f9d87..cf445f0d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'package:bitsdojo_window/bitsdojo_window.dart'; import 'package:flutter/material.dart'; import 'package:system_theme/system_theme.dart'; +import 'logic/route_args.dart'; import 'logic/theme.dart' show buildThemeData; import 'views/desktop.dart'; import 'views/lock.dart'; @@ -43,15 +44,6 @@ void main(List argsList) async { }); } -String? _getUserName(BuildContext context) { - final route = ModalRoute.of(context); - if (route == null) return null; - if (route.settings.arguments == null) return null; - - final args = route.settings.arguments as Map; - return args['userName']; -} - class GenesisShellApp extends StatelessWidget { const GenesisShellApp({ super.key, @@ -78,8 +70,14 @@ class GenesisShellApp extends StatelessWidget { ), themeMode: ThemeMode.dark, routes: { - '/': (context) => DesktopView(userName: _getUserName(context)), - '/lock': (context) => LockView(userName: _getUserName(context)), + '/': (context) { + final args = AuthedRouteArguments.of(context); + return DesktopView( + userName: args.userName, + isSession: args.isSession, + ); + }, + '/lock': (context) => LockView(userName: AuthedRouteArguments.of(context).userName), '/login': (_) => const LoginView(), }, initialRoute: initLocked ? '/lock' : (displayManager ? '/login' : '/'), diff --git a/lib/views/desktop.dart b/lib/views/desktop.dart index 824decad..c3a9de98 100644 --- a/lib/views/desktop.dart +++ b/lib/views/desktop.dart @@ -14,18 +14,21 @@ class DesktopView extends StatefulWidget { this.desktopWallpaper = null, this.mobileWallpaper = null, this.userName = null, + this.isSession = false, }); final String? wallpaper; final String? desktopWallpaper; final String? mobileWallpaper; final String? userName; + final bool isSession; @override State createState() => _DesktopViewState(); } class _DesktopViewState extends State { + static const authChannel = MethodChannel('com.expidusos.genesis.shell/auth'); static const sessionChannel = MethodChannel('com.expidusos.genesis.shell/session'); String? sessionName = null; @@ -48,6 +51,12 @@ class _DesktopViewState extends State { sessionChannel.invokeMethod('close', sessionName).catchError((err) { print(err); }); + + if (widget.isSession && widget.userName != null) { + authChannel.invokeMethod('deauth', widget.userName).catchError((err) { + print(err); + }); + } } @override diff --git a/lib/views/login.dart b/lib/views/login.dart index b15e7cf2..b694fcb3 100644 --- a/lib/views/login.dart +++ b/lib/views/login.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; import 'package:intl/intl.dart'; +import '../logic/route_args.dart'; import '../logic/wallpaper.dart'; import '../widgets/account_profile.dart'; @@ -104,13 +105,15 @@ class _LoginViewState extends State { ), const Spacer(), LoginPrompt( + isSession: true, onLogin: () { final nav = Navigator.of(context); nav.pushNamed( '/', - arguments: { - 'userName': _selectedUser!, - }, + arguments: AuthedRouteArguments( + userName: _selectedUser!, + isSession: true, + ), ); }, ), diff --git a/lib/widgets/login.dart b/lib/widgets/login.dart index e1a5446a..98b9ee65 100644 --- a/lib/widgets/login.dart +++ b/lib/widgets/login.dart @@ -7,10 +7,12 @@ class LoginPrompt extends StatefulWidget { const LoginPrompt({ super.key, this.name, + this.isSession = false, required this.onLogin, }); final String? name; + final bool isSession; final VoidCallback onLogin; @override @@ -32,6 +34,7 @@ class _LoginPromptState extends State { var args = { 'password': input, + 'session': widget.isSession, }; if (widget.name != null) { diff --git a/lib/widgets/power.dart b/lib/widgets/power.dart index 3d63f209..ffcccab6 100644 --- a/lib/widgets/power.dart +++ b/lib/widgets/power.dart @@ -42,7 +42,7 @@ class PowerDialog extends StatelessWidget { IconButton( onPressed: () { final nav = Navigator.of(context); - nav.popUntil(ModalRoute.withName(nav.widget.initialRoute ?? '/')); + nav.popUntil(ModalRoute.withName('/')); if (nav.canPop()) nav.pop(); else SystemNavigator.pop(); diff --git a/linux/application-priv.h b/linux/application-priv.h index 8eed371c..b7e96125 100644 --- a/linux/application-priv.h +++ b/linux/application-priv.h @@ -1,5 +1,5 @@ #pragma once - +#include "channels/auth.h" #include "channels/session.h" struct _GenesisShellApplication { @@ -7,7 +7,7 @@ struct _GenesisShellApplication { char** dart_entrypoint_arguments; FlMethodChannel* outputs; FlMethodChannel* account; - FlMethodChannel* auth; + AuthChannel auth; SessionChannel session; GtkWindow* win; }; diff --git a/linux/application.cc b/linux/application.cc index 0ad3b1b6..3595f5fd 100644 --- a/linux/application.cc +++ b/linux/application.cc @@ -2,7 +2,6 @@ #include "application.h" #include "application-priv.h" -#include "channels/auth.h" #include "channels/account.h" #include "channels/outputs.h" @@ -46,12 +45,7 @@ static void genesis_shell_application_activate(GApplication* application) { fl_method_channel_set_method_call_handler(self->account, account_method_call_handler, self, nullptr); } - { - g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); - self->auth = fl_method_channel_new(fl_engine_get_binary_messenger(fl_view_get_engine(view)), "com.expidusos.genesis.shell/auth", FL_METHOD_CODEC(codec)); - fl_method_channel_set_method_call_handler(self->auth, auth_method_call_handler, self, nullptr); - } - + auth_channel_init(&self->auth, view); session_channel_init(&self->session, view); gtk_widget_grab_focus(GTK_WIDGET(view)); } @@ -98,8 +92,8 @@ static void genesis_shell_application_dispose(GObject* object) { GenesisShellApplication* self = GENESIS_SHELL_APPLICATION(object); g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); g_clear_object(&self->outputs); - g_clear_object(&self->auth); g_clear_object(&self->account); + auth_channel_deinit(&self->auth); session_channel_deinit(&self->session); G_OBJECT_CLASS(genesis_shell_application_parent_class)->dispose(object); } diff --git a/linux/channels/auth.cc b/linux/channels/auth.cc index d738e089..3e09df46 100644 --- a/linux/channels/auth.cc +++ b/linux/channels/auth.cc @@ -19,7 +19,9 @@ static int conversation(int num_msg, const struct pam_message** msg, struct pam_ return PAM_SUCCESS; } -void auth_method_call_handler(FlMethodChannel* channel, FlMethodCall* method_call, gpointer user_data) { +static void method_call_handler(FlMethodChannel* channel, FlMethodCall* method_call, gpointer user_data) { + AuthChannel* self = (AuthChannel*)user_data; + g_autoptr(FlMethodResponse) response = nullptr; if (strcmp(fl_method_call_get_name(method_call), "auth") == 0) { FlValue* args = fl_method_call_get_args(method_call); @@ -62,16 +64,56 @@ void auth_method_call_handler(FlMethodChannel* channel, FlMethodCall* method_cal r = pam_setcred(handle, PAM_REFRESH_CRED); if (r != PAM_SUCCESS) { - fl_method_call_respond_error(method_call, "PAM", "pam_acct_mgmt failed", fl_value_new_string(pam_strerror(handle, r)), NULL); + fl_method_call_respond_error(method_call, "PAM", "pam_setcred failed", fl_value_new_string(pam_strerror(handle, r)), NULL); pam_end(handle, r); free(conv); return; } - pam_end(handle, r); - free(conv); + bool isSession = false; + + if (fl_value_lookup_string(args, "session") != nullptr) { + isSession = fl_value_get_bool(fl_value_lookup_string(args, "session")); + } + + if (isSession) { + if (g_hash_table_contains(self->sessions, username)) { + fl_method_call_respond_error(method_call, "Linux", "User session is already running", NULL, NULL); + pam_end(handle, r); + free(conv); + return; + } + + r = pam_open_session(handle, PAM_SILENT); + if (r != PAM_SUCCESS) { + fl_method_call_respond_error(method_call, "PAM", "pam_open_session failed", fl_value_new_string(pam_strerror(handle, r)), NULL); + pam_end(handle, r); + free(conv); + return; + } + + g_hash_table_insert(self->sessions, (gpointer)username, (gpointer)handle); + } else { + pam_end(handle, r); + free(conv); + } + + response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + } else if (strcmp(fl_method_call_get_name(method_call), "deauth") == 0) { + FlValue* args = fl_method_call_get_args(method_call); + const gchar* name = fl_value_get_string(args); + if (!g_hash_table_contains(self->sessions, name)) { + fl_method_call_respond_error(method_call, "Linux", "User does not have a session", NULL, NULL); + return; + } + + g_hash_table_remove(self->sessions, name); response = FL_METHOD_RESPONSE(fl_method_success_response_new(nullptr)); + } else if (strcmp(fl_method_call_get_name(method_call), "hasSession") == 0) { + FlValue* args = fl_method_call_get_args(method_call); + const gchar* name = fl_value_get_string(args); + response = FL_METHOD_RESPONSE(fl_method_success_response_new(fl_value_new_bool(g_hash_table_contains(self->sessions, name)))); } else { response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); } @@ -81,3 +123,21 @@ void auth_method_call_handler(FlMethodChannel* channel, FlMethodCall* method_cal g_warning("Failed to send response: %s", error->message); } } + +static void destory_session(pam_handle_t* handle) { + pam_close_session(handle, PAM_SILENT); + pam_end(handle, PAM_SUCCESS); +} + +void auth_channel_init(AuthChannel* self, FlView* view) { + self->sessions = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)destory_session); + + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + self->channel = fl_method_channel_new(fl_engine_get_binary_messenger(fl_view_get_engine(view)), "com.expidusos.genesis.shell/auth", FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler(self->channel, method_call_handler, self, nullptr); +} + +void auth_channel_deinit(AuthChannel* self) { + g_clear_object(&self->channel); + g_hash_table_unref(self->sessions); +} diff --git a/linux/channels/auth.h b/linux/channels/auth.h index 89935b6d..38cfa719 100644 --- a/linux/channels/auth.h +++ b/linux/channels/auth.h @@ -4,4 +4,10 @@ #include "../application.h" -void auth_method_call_handler(FlMethodChannel* channel, FlMethodCall* method_call, gpointer user_data); +typedef struct _AuthChannel { + GHashTable* sessions; + FlMethodChannel* channel; +} AuthChannel; + +void auth_channel_init(AuthChannel* self, FlView* view); +void auth_channel_deinit(AuthChannel* self); diff --git a/linux/data/pam b/linux/data/pam index 9c29f058..26b491d3 100644 --- a/linux/data/pam +++ b/linux/data/pam @@ -1,3 +1,6 @@ # Genesis Shell login management -auth include login +auth substack login +account include login +password substack login +session include login diff --git a/nix/module.nix b/nix/module.nix index dec28790..7a5f2130 100644 --- a/nix/module.nix +++ b/nix/module.nix @@ -19,7 +19,11 @@ ]; }; - security.pam.services.genesis-shell.text = lib.readFile ../linux/data/pam; + security.pam.services.genesis-shell = { + allowNullPassword = true; + startSession = true; + enableGnomeKeyring = lib.mkDefault config.services.gnome.gnome-keyring.enable; + }; nix.enable = false;