diff --git a/README.md b/README.md index 8bbeb11..3cd8602 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,49 @@ -# Information +# Flutter Rust App with Privilege Elevation -![rustdesk-banner](https://user-images.githubusercontent.com/71636191/236513788-89da3f2a-6898-4e30-a12f-b5af129858c3.png) +This application incorporates Rust code for executing privileged operations using `sudo` or `polkit` (if available). This combination of Flutter and Rust allows for a powerful and secure application with elevated privileges when necessary. -### Company information +## Prerequisites -Virtual / remote desktop infrastructure for everyone! Open source TeamViewer / Citrix alternative. +Make sure you have the following installed before proceeding: -### Why participate in an Octernship with RustDesk +- Flutter SDK: [Installation Guide](https://flutter.dev/docs/get-started/install) +- Rust Toolchain (comes with the compiler): [Installation Guide](https://www.rustup.rs) +- C/C++ Compiler (for building Rust FFI): Ensure you have a compatible C/C++ compiler for your platform. +- [flutter-rust-bridge flutter package](https://pub.dev/packages/flutter_rust_bridge) -We use Rust + Flutter and cover all platforms. [RustDesk](https://github.com/rustdesk/rustdesk) is a 40K+ GitHub Stars project. You can learn Rust and Flutter very well here. +## Getting Started -### Octernship role description +To run the Flutter Rust app with privilege elevation, follow these steps: -| Octernship info | Timelines and Stipend | -| ------------- | ------------- | -| Assignment Deadline | 20 June 2023 | -| Octernship Duration | 3 Months | -| Monthly Stipend | $500 USD | +1. Clone the repository: -### Recommended qualifications +2. Navigate to the project directory: -- Experience with Git & GitHub -- Experience with networking and basic Linux commands -- Passion to learn Rust and Flutter -- Passion for open source -- Fast learner +3. Run the Just command code: -### Eligibility + ```bash + just gen + ``` -To participate, you must be: +4. Build and run the Flutter app: -* A [verified student](https://education.github.com/discount_requests/pack_application) on Global Campus + ```bash + flutter run + ``` -* 18 years or older +## Screenshots +| ![Welcome Screen](assets/home_ss.png) | +|:--:| +| *The welcome screen the user is greeted with when opening the application* | -* Active contributor on GitHub (monthly) +| !['Show my root folder' Button](assets/get_root.png) | +|:--:| +| *This button prompts the user to enter their password. It elevates privilege with the sudo command* | -# Assignment +| ![Password Input Dialog](assets/sudo.png) | +|:--:| +| *The dialog that allows the user input their password* | -## Elevate priviledge to run a Linux command with Rust - -The starter code is included in this repository. ‼️ - -### Setting up 🖥️ - -* Prepare your Linux environment -* Follow https://github.com/Desdaemon/flutter_rust_bridge_template/blob/main/README.md to get started -* `flutter run -d linux` -![image](https://user-images.githubusercontent.com/71636191/231404421-a203e923-0c51-42fd-9ee7-cea0ea44fdd9.png) - -### Task instructions - -Your task is to - -- Elevate priviledge to run `ls -la /root/` with Rust, and print the result on the Flutter window - -### Task Expectations - -- Polkit is usually used by priviledge elevation on Linux -- Polkit may be disabled or uninstalled on some system, you need to detect if polkit works well, and elevate priviledge with alternative way - -### Task submission - -Students are expected to use the [GitHub Flow](https://docs.github.com/en/get-started/quickstart/github-flow) when working on their project. - -1. Creating a new branch -2. Making changes on the new branch -3. Creating a Pull Request to merge changes into main branch on or before the assignment deadline. -3. Using GitHub Discussions to ask any relevant questions regarding the project - -### Resources - -* https://github.com/rustdesk/rustdesk/issues/2756 - -## Questions -We love to answer questions! We highly recommend learning by doing and looking up the questions before reaching out. If you were unable to find your solution on ChatGPT / Google / Stack Overflow, please open a new discussion in this repository. +| ![Output](assets/result.png) | +|:--:| +| *The results displayed on the screen* | \ No newline at end of file diff --git a/assets/get_root.png b/assets/get_root.png new file mode 100644 index 0000000..4c6692c Binary files /dev/null and b/assets/get_root.png differ diff --git a/assets/home_ss.png b/assets/home_ss.png new file mode 100644 index 0000000..0621fa5 Binary files /dev/null and b/assets/home_ss.png differ diff --git a/assets/result.png b/assets/result.png new file mode 100644 index 0000000..c45142a Binary files /dev/null and b/assets/result.png differ diff --git a/assets/sudo.png b/assets/sudo.png new file mode 100644 index 0000000..4ff429c Binary files /dev/null and b/assets/sudo.png differ diff --git a/ios/Runner/bridge_generated.h b/ios/Runner/bridge_generated.h index ae8c386..7b9ac1c 100644 --- a/ios/Runner/bridge_generated.h +++ b/ios/Runner/bridge_generated.h @@ -9,6 +9,11 @@ typedef int64_t DartPort; typedef bool (*DartPostCObjectFnType)(DartPort port_id, void *message); +typedef struct wire_uint_8_list { + uint8_t *ptr; + int32_t len; +} wire_uint_8_list; + typedef struct DartCObject *WireSyncReturn; void store_dart_post_cobject(DartPostCObjectFnType ptr); @@ -21,20 +26,26 @@ uintptr_t new_dart_opaque(Dart_Handle handle); intptr_t init_frb_dart_api_dl(void *obj); -void wire_platform(int64_t port_); +void wire_get_username(int64_t port_); + +void wire_print_root_folder(int64_t port_, struct wire_uint_8_list *password); + +void wire_check_polkit(int64_t port_); -void wire_rust_release_mode(int64_t port_); +struct wire_uint_8_list *new_uint_8_list_0(int32_t len); void free_WireSyncReturn(WireSyncReturn ptr); static int64_t dummy_method_to_enforce_bundling(void) { int64_t dummy_var = 0; - dummy_var ^= ((int64_t) (void*) wire_platform); - dummy_var ^= ((int64_t) (void*) wire_rust_release_mode); + dummy_var ^= ((int64_t) (void*) wire_get_username); + dummy_var ^= ((int64_t) (void*) wire_print_root_folder); + dummy_var ^= ((int64_t) (void*) wire_check_polkit); + dummy_var ^= ((int64_t) (void*) new_uint_8_list_0); dummy_var ^= ((int64_t) (void*) free_WireSyncReturn); dummy_var ^= ((int64_t) (void*) store_dart_post_cobject); dummy_var ^= ((int64_t) (void*) get_dart_object); dummy_var ^= ((int64_t) (void*) drop_dart_object); dummy_var ^= ((int64_t) (void*) new_dart_opaque); return dummy_var; -} \ No newline at end of file +} diff --git a/lib/bridge_definitions.dart b/lib/bridge_definitions.dart index fadd6b6..077dd8b 100644 --- a/lib/bridge_definitions.dart +++ b/lib/bridge_definitions.dart @@ -1,6 +1,6 @@ // AUTO GENERATED FILE, DO NOT EDIT. -// Generated by `flutter_rust_bridge`@ 1.62.1. -// ignore_for_file: non_constant_identifier_names, unused_element, duplicate_ignore, directives_ordering, curly_braces_in_flow_control_structures, unnecessary_lambdas, slash_for_doc_comments, prefer_const_literals_to_create_immutables, implicit_dynamic_list_literal, duplicate_import, unused_import, unnecessary_import, prefer_single_quotes, prefer_const_constructors, use_super_parameters, always_use_package_imports, annotate_overrides, invalid_use_of_protected_member, constant_identifier_names, invalid_use_of_internal_member +// Generated by `flutter_rust_bridge`@ 1.77.1. +// ignore_for_file: non_constant_identifier_names, unused_element, duplicate_ignore, directives_ordering, curly_braces_in_flow_control_structures, unnecessary_lambdas, slash_for_doc_comments, prefer_const_literals_to_create_immutables, implicit_dynamic_list_literal, duplicate_import, unused_import, unnecessary_import, prefer_single_quotes, prefer_const_constructors, use_super_parameters, always_use_package_imports, annotate_overrides, invalid_use_of_protected_member, constant_identifier_names, invalid_use_of_internal_member, prefer_is_empty, unnecessary_const import 'bridge_generated.io.dart' if (dart.library.html) 'bridge_generated.web.dart'; @@ -8,24 +8,43 @@ import 'dart:convert'; import 'dart:async'; import 'package:meta/meta.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge.dart'; +import 'package:uuid/uuid.dart'; abstract class Native { - Future platform({dynamic hint}); + /// Rust function that retrieves the username of the current user by executing a terminal command. + /// It utilizes the underlying operating system's capabilities to obtain the username. + /// Returns a string value. + /// + /// ### Example + /// ```rust + /// fn main() { + /// let username = get_username(); // returns current user's username + /// println!("Hello, {}!", username); + /// } + /// ``` + Future getUsername({dynamic hint}); - FlutterRustBridgeTaskConstMeta get kPlatformConstMeta; + FlutterRustBridgeTaskConstMeta get kGetUsernameConstMeta; - Future rustReleaseMode({dynamic hint}); + /// Rust function that uses the sudo command to elevate privilege. + /// Takes in a password as a String argument to grant access to the root folder. + /// + /// ### Return Values + /// - Returns Some(String) if the function was successfullt executed and the proper rights have been granted. + /// - Returns None if the function fails to execute correctly and the proper rights have not been given to the administrator. + /// + /// ### Example + /// ```rust + /// fn main() { + /// let return_statement = print_root_folder("pass1234".to_string()); + /// println("{:?}", return_statement); // prints the output statement provided the password is correct or throws an error. + /// } + /// ``` + Future printRootFolder({required String password, dynamic hint}); - FlutterRustBridgeTaskConstMeta get kRustReleaseModeConstMeta; -} + FlutterRustBridgeTaskConstMeta get kPrintRootFolderConstMeta; + + Future checkPolkit({dynamic hint}); -enum Platform { - Unknown, - Android, - Ios, - Windows, - Unix, - MacIntel, - MacApple, - Wasm, + FlutterRustBridgeTaskConstMeta get kCheckPolkitConstMeta; } diff --git a/lib/bridge_generated.dart b/lib/bridge_generated.dart index 7501e4b..6cafcc9 100644 --- a/lib/bridge_generated.dart +++ b/lib/bridge_generated.dart @@ -1,12 +1,13 @@ // AUTO GENERATED FILE, DO NOT EDIT. -// Generated by `flutter_rust_bridge`@ 1.62.1. -// ignore_for_file: non_constant_identifier_names, unused_element, duplicate_ignore, directives_ordering, curly_braces_in_flow_control_structures, unnecessary_lambdas, slash_for_doc_comments, prefer_const_literals_to_create_immutables, implicit_dynamic_list_literal, duplicate_import, unused_import, unnecessary_import, prefer_single_quotes, prefer_const_constructors, use_super_parameters, always_use_package_imports, annotate_overrides, invalid_use_of_protected_member, constant_identifier_names, invalid_use_of_internal_member +// Generated by `flutter_rust_bridge`@ 1.77.1. +// ignore_for_file: non_constant_identifier_names, unused_element, duplicate_ignore, directives_ordering, curly_braces_in_flow_control_structures, unnecessary_lambdas, slash_for_doc_comments, prefer_const_literals_to_create_immutables, implicit_dynamic_list_literal, duplicate_import, unused_import, unnecessary_import, prefer_single_quotes, prefer_const_constructors, use_super_parameters, always_use_package_imports, annotate_overrides, invalid_use_of_protected_member, constant_identifier_names, invalid_use_of_internal_member, prefer_is_empty, unnecessary_const import "bridge_definitions.dart"; import 'dart:convert'; import 'dart:async'; import 'package:meta/meta.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge.dart'; +import 'package:uuid/uuid.dart'; import 'bridge_generated.io.dart' if (dart.library.html) 'bridge_generated.web.dart'; @@ -19,35 +20,52 @@ class NativeImpl implements Native { factory NativeImpl.wasm(FutureOr module) => NativeImpl(module as ExternalLibrary); NativeImpl.raw(this._platform); - Future platform({dynamic hint}) { + Future getUsername({dynamic hint}) { return _platform.executeNormal(FlutterRustBridgeTask( - callFfi: (port_) => _platform.inner.wire_platform(port_), - parseSuccessData: _wire2api_platform, - constMeta: kPlatformConstMeta, + callFfi: (port_) => _platform.inner.wire_get_username(port_), + parseSuccessData: _wire2api_String, + constMeta: kGetUsernameConstMeta, argValues: [], hint: hint, )); } - FlutterRustBridgeTaskConstMeta get kPlatformConstMeta => + FlutterRustBridgeTaskConstMeta get kGetUsernameConstMeta => const FlutterRustBridgeTaskConstMeta( - debugName: "platform", + debugName: "get_username", argNames: [], ); - Future rustReleaseMode({dynamic hint}) { + Future printRootFolder({required String password, dynamic hint}) { + var arg0 = _platform.api2wire_String(password); return _platform.executeNormal(FlutterRustBridgeTask( - callFfi: (port_) => _platform.inner.wire_rust_release_mode(port_), - parseSuccessData: _wire2api_bool, - constMeta: kRustReleaseModeConstMeta, + callFfi: (port_) => _platform.inner.wire_print_root_folder(port_, arg0), + parseSuccessData: _wire2api_opt_String, + constMeta: kPrintRootFolderConstMeta, + argValues: [password], + hint: hint, + )); + } + + FlutterRustBridgeTaskConstMeta get kPrintRootFolderConstMeta => + const FlutterRustBridgeTaskConstMeta( + debugName: "print_root_folder", + argNames: ["password"], + ); + + Future checkPolkit({dynamic hint}) { + return _platform.executeNormal(FlutterRustBridgeTask( + callFfi: (port_) => _platform.inner.wire_check_polkit(port_), + parseSuccessData: _wire2api_opt_String, + constMeta: kCheckPolkitConstMeta, argValues: [], hint: hint, )); } - FlutterRustBridgeTaskConstMeta get kRustReleaseModeConstMeta => + FlutterRustBridgeTaskConstMeta get kCheckPolkitConstMeta => const FlutterRustBridgeTaskConstMeta( - debugName: "rust_release_mode", + debugName: "check_polkit", argNames: [], ); @@ -56,19 +74,28 @@ class NativeImpl implements Native { } // Section: wire2api - bool _wire2api_bool(dynamic raw) { - return raw as bool; + String _wire2api_String(dynamic raw) { + return raw as String; + } + + String? _wire2api_opt_String(dynamic raw) { + return raw == null ? null : _wire2api_String(raw); } - int _wire2api_i32(dynamic raw) { + int _wire2api_u8(dynamic raw) { return raw as int; } - Platform _wire2api_platform(dynamic raw) { - return Platform.values[raw]; + Uint8List _wire2api_uint_8_list(dynamic raw) { + return raw as Uint8List; } } // Section: api2wire +@protected +int api2wire_u8(int raw) { + return raw; +} + // Section: finalizer diff --git a/lib/bridge_generated.io.dart b/lib/bridge_generated.io.dart index 5ec0e30..a1ca384 100644 --- a/lib/bridge_generated.io.dart +++ b/lib/bridge_generated.io.dart @@ -1,12 +1,13 @@ // AUTO GENERATED FILE, DO NOT EDIT. -// Generated by `flutter_rust_bridge`@ 1.62.1. -// ignore_for_file: non_constant_identifier_names, unused_element, duplicate_ignore, directives_ordering, curly_braces_in_flow_control_structures, unnecessary_lambdas, slash_for_doc_comments, prefer_const_literals_to_create_immutables, implicit_dynamic_list_literal, duplicate_import, unused_import, unnecessary_import, prefer_single_quotes, prefer_const_constructors, use_super_parameters, always_use_package_imports, annotate_overrides, invalid_use_of_protected_member, constant_identifier_names, invalid_use_of_internal_member +// Generated by `flutter_rust_bridge`@ 1.77.1. +// ignore_for_file: non_constant_identifier_names, unused_element, duplicate_ignore, directives_ordering, curly_braces_in_flow_control_structures, unnecessary_lambdas, slash_for_doc_comments, prefer_const_literals_to_create_immutables, implicit_dynamic_list_literal, duplicate_import, unused_import, unnecessary_import, prefer_single_quotes, prefer_const_constructors, use_super_parameters, always_use_package_imports, annotate_overrides, invalid_use_of_protected_member, constant_identifier_names, invalid_use_of_internal_member, prefer_is_empty, unnecessary_const import "bridge_definitions.dart"; import 'dart:convert'; import 'dart:async'; import 'package:meta/meta.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge.dart'; +import 'package:uuid/uuid.dart'; import 'bridge_generated.dart'; export 'bridge_generated.dart'; import 'dart:ffi' as ffi; @@ -16,6 +17,17 @@ class NativePlatform extends FlutterRustBridgeBase { // Section: api2wire + @protected + ffi.Pointer api2wire_String(String raw) { + return api2wire_uint_8_list(utf8.encoder.convert(raw)); + } + + @protected + ffi.Pointer api2wire_uint_8_list(Uint8List raw) { + final ans = inner.new_uint_8_list_0(raw.length); + ans.ref.ptr.asTypedList(raw.length).setAll(0, raw); + return ans; + } // Section: finalizer // Section: api_fill_to_wire @@ -26,6 +38,7 @@ class NativePlatform extends FlutterRustBridgeBase { // AUTO GENERATED FILE, DO NOT EDIT. // // Generated by `package:ffigen`. +// ignore_for_file: type=lint /// generated by flutter_rust_bridge class NativeWire implements FlutterRustBridgeWireBase { @@ -116,33 +129,65 @@ class NativeWire implements FlutterRustBridgeWireBase { late final _init_frb_dart_api_dl = _init_frb_dart_api_dlPtr .asFunction)>(); - void wire_platform( + void wire_get_username( int port_, ) { - return _wire_platform( + return _wire_get_username( port_, ); } - late final _wire_platformPtr = + late final _wire_get_usernamePtr = _lookup>( - 'wire_platform'); - late final _wire_platform = - _wire_platformPtr.asFunction(); + 'wire_get_username'); + late final _wire_get_username = + _wire_get_usernamePtr.asFunction(); + + void wire_print_root_folder( + int port_, + ffi.Pointer password, + ) { + return _wire_print_root_folder( + port_, + password, + ); + } - void wire_rust_release_mode( + late final _wire_print_root_folderPtr = _lookup< + ffi.NativeFunction< + ffi.Void Function(ffi.Int64, + ffi.Pointer)>>('wire_print_root_folder'); + late final _wire_print_root_folder = _wire_print_root_folderPtr + .asFunction)>(); + + void wire_check_polkit( int port_, ) { - return _wire_rust_release_mode( + return _wire_check_polkit( port_, ); } - late final _wire_rust_release_modePtr = + late final _wire_check_polkitPtr = _lookup>( - 'wire_rust_release_mode'); - late final _wire_rust_release_mode = - _wire_rust_release_modePtr.asFunction(); + 'wire_check_polkit'); + late final _wire_check_polkit = + _wire_check_polkitPtr.asFunction(); + + ffi.Pointer new_uint_8_list_0( + int len, + ) { + return _new_uint_8_list_0( + len, + ); + } + + late final _new_uint_8_list_0Ptr = _lookup< + ffi.NativeFunction< + ffi.Pointer Function( + ffi.Int32)>>('new_uint_8_list_0'); + late final _new_uint_8_list_0 = _new_uint_8_list_0Ptr + .asFunction Function(int)>(); void free_WireSyncReturn( WireSyncReturn ptr, @@ -159,8 +204,16 @@ class NativeWire implements FlutterRustBridgeWireBase { _free_WireSyncReturnPtr.asFunction(); } -class _Dart_Handle extends ffi.Opaque {} +final class _Dart_Handle extends ffi.Opaque {} + +final class wire_uint_8_list extends ffi.Struct { + external ffi.Pointer ptr; + + @ffi.Int32() + external int len; +} typedef DartPostCObjectFnType = ffi.Pointer< - ffi.NativeFunction)>>; + ffi.NativeFunction< + ffi.Bool Function(DartPort port_id, ffi.Pointer message)>>; typedef DartPort = ffi.Int64; diff --git a/lib/bridge_generated.web.dart b/lib/bridge_generated.web.dart index bdcbb06..d82be87 100644 --- a/lib/bridge_generated.web.dart +++ b/lib/bridge_generated.web.dart @@ -1,12 +1,13 @@ // AUTO GENERATED FILE, DO NOT EDIT. -// Generated by `flutter_rust_bridge`@ 1.62.1. -// ignore_for_file: non_constant_identifier_names, unused_element, duplicate_ignore, directives_ordering, curly_braces_in_flow_control_structures, unnecessary_lambdas, slash_for_doc_comments, prefer_const_literals_to_create_immutables, implicit_dynamic_list_literal, duplicate_import, unused_import, unnecessary_import, prefer_single_quotes, prefer_const_constructors, use_super_parameters, always_use_package_imports, annotate_overrides, invalid_use_of_protected_member, constant_identifier_names, invalid_use_of_internal_member +// Generated by `flutter_rust_bridge`@ 1.77.1. +// ignore_for_file: non_constant_identifier_names, unused_element, duplicate_ignore, directives_ordering, curly_braces_in_flow_control_structures, unnecessary_lambdas, slash_for_doc_comments, prefer_const_literals_to_create_immutables, implicit_dynamic_list_literal, duplicate_import, unused_import, unnecessary_import, prefer_single_quotes, prefer_const_constructors, use_super_parameters, always_use_package_imports, annotate_overrides, invalid_use_of_protected_member, constant_identifier_names, invalid_use_of_internal_member, prefer_is_empty, unnecessary_const import "bridge_definitions.dart"; import 'dart:convert'; import 'dart:async'; import 'package:meta/meta.dart'; import 'package:flutter_rust_bridge/flutter_rust_bridge.dart'; +import 'package:uuid/uuid.dart'; import 'bridge_generated.dart'; export 'bridge_generated.dart'; @@ -19,6 +20,15 @@ class NativePlatform extends FlutterRustBridgeBase // Section: api2wire + @protected + String api2wire_String(String raw) { + return raw; + } + + @protected + Uint8List api2wire_uint_8_list(Uint8List raw) { + return raw; + } // Section: finalizer } @@ -32,9 +42,12 @@ external NativeWasmModule get wasmModule; class NativeWasmModule implements WasmModule { external Object /* Promise */ call([String? moduleName]); external NativeWasmModule bind(dynamic thisArg, String moduleName); - external dynamic /* void */ wire_platform(NativePortType port_); + external dynamic /* void */ wire_get_username(NativePortType port_); - external dynamic /* void */ wire_rust_release_mode(NativePortType port_); + external dynamic /* void */ wire_print_root_folder( + NativePortType port_, String password); + + external dynamic /* void */ wire_check_polkit(NativePortType port_); } // Section: WASM wire connector @@ -43,8 +56,12 @@ class NativeWire extends FlutterRustBridgeWasmWireBase { NativeWire(FutureOr module) : super(WasmModule.cast(module)); - void wire_platform(NativePortType port_) => wasmModule.wire_platform(port_); + void wire_get_username(NativePortType port_) => + wasmModule.wire_get_username(port_); + + void wire_print_root_folder(NativePortType port_, String password) => + wasmModule.wire_print_root_folder(port_, password); - void wire_rust_release_mode(NativePortType port_) => - wasmModule.wire_rust_release_mode(port_); + void wire_check_polkit(NativePortType port_) => + wasmModule.wire_check_polkit(port_); } diff --git a/lib/globals.dart b/lib/globals.dart new file mode 100644 index 0000000..e56f4f0 --- /dev/null +++ b/lib/globals.dart @@ -0,0 +1 @@ +String username = ""; diff --git a/lib/main.dart b/lib/main.dart index a334127..aea5d17 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,12 @@ import 'package:flutter/material.dart'; -import 'ffi.dart' if (dart.library.html) 'ffi_web.dart'; +import 'ffi.dart'; +import 'globals.dart'; +import 'screens/screens.dart'; -void main() { +Future main() async { + await api.getUsername().then((value) { + username = value; + }); runApp(const MyApp()); } @@ -10,40 +15,10 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( + return const MaterialApp( + debugShowCheckedModeBanner: false, title: 'Octernships Project', - theme: ThemeData( - primarySwatch: Colors.blue, - ), - home: const MyHomePage(title: 'Elevate priviledge to run a Linux command'), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({Key? key, required this.title}) : super(key: key); - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text(widget.title), - ), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text("ls -la /root/", style: TextStyle(fontSize: 40.0)), - const Text('Run above cmd with Rust and print the output here'), - ], - ), - ), + home: WelcomeScreen(), ); } } diff --git a/lib/screens/polkit_check.dart b/lib/screens/polkit_check.dart new file mode 100644 index 0000000..678b6db --- /dev/null +++ b/lib/screens/polkit_check.dart @@ -0,0 +1,70 @@ +import 'package:blinking_text/blinking_text.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +import '../ffi.dart'; + +class CheckPolkit extends StatefulWidget { + const CheckPolkit({super.key}); + + @override + State createState() => _CheckPolkitState(); +} + +class _CheckPolkitState extends State { + bool status = true; + bool isVisible = true; + + Future checkStatus() async { + var thing = await api.checkPolkit(); + if (thing!.isNotEmpty) { + print("An error occured"); + setState(() { + status = false; + }); + } else { + print("Polkit dey"); + setState(() { + status = true; + }); + } + } + + @override + void initState() { + super.initState(); + timer(); + checkStatus(); + } + + Future timer() async { + Future.delayed(const Duration(seconds: 5), () { + setState(() { + isVisible = false; + }); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: Center( + child: Visibility( + visible: isVisible, + replacement: Text(!status + ? "Seems like you don't have polkit set up." + : "We detected polkit on your system. Proceed?"), + child: BlinkText( + beginColor: const Color(0xFFBDBDBD), + "Checking if your system has polkit...", + style: GoogleFonts.nunito( + fontSize: 50, + fontWeight: FontWeight.bold, + color: const Color(0xFFBDBDBD)), + endColor: Colors.transparent, + ), + )), + ); + } +} diff --git a/lib/screens/screens.dart b/lib/screens/screens.dart new file mode 100644 index 0000000..7088529 --- /dev/null +++ b/lib/screens/screens.dart @@ -0,0 +1,3 @@ +export 'sudo_page.dart'; +export 'welcome_screen.dart'; +export 'polkit_check.dart'; diff --git a/lib/screens/sudo_page.dart b/lib/screens/sudo_page.dart new file mode 100644 index 0000000..1bbada3 --- /dev/null +++ b/lib/screens/sudo_page.dart @@ -0,0 +1,147 @@ +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +import '../ffi.dart'; +import '../globals.dart'; + +class SudoCommandPage extends StatefulWidget { + const SudoCommandPage({Key? key}) : super(key: key); + + @override + State createState() => _SudoCommandPageState(); +} + +class _SudoCommandPageState extends State { + String dir = ""; + + Future returnFolder(String password) async { + await api.printRootFolder(password: password).then((value) { + setState(() { + dir = value!; + }); + }); + } + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Center( + child: ElevatedButton( + onPressed: () { + passwordDialog(); + }, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.lightBlue, + padding: const EdgeInsets.symmetric( + vertical: 20, horizontal: 35), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(10))), + child: const Text( + "Show me my Root Folder", + style: TextStyle( + fontWeight: FontWeight.w600, color: Colors.white), + )), + ), + const SizedBox(height: 20), + Container( + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + color: const Color(0xFFF5F5F5), + ), + child: Text( + dir, + style: GoogleFonts.robotoMono( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.black), + ), + ) + ], + ), + ); + } + + void passwordDialog() { + showDialog( + context: context, + builder: (BuildContext dialogContext) { + String password = ""; + return AlertDialog( + backgroundColor: const Color(0xFFE0E0E0), + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), + content: + Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + Text("[sudo] enter password for $username: "), + const SizedBox(width: 10), + SizedBox( + width: 100, + height: 40, + child: TextField( + onChanged: (value) { + password = value; + }, + obscureText: true, + ), + ) + ]), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text("Back"), + ), + TextButton( + onPressed: () async { + setState(() { + dir = ""; + }); + await returnFolder(password).then((value) { + if (dir.isEmpty) { + showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + backgroundColor: const Color(0xFFE0E0E0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20)), + title: const Text('Incorrect Password'), + content: const Text('Please try again.'), + actions: [ + TextButton( + child: const Text('OK'), + onPressed: () { + Navigator.of(context) + .pop(); // Close the dialog + }, + ), + ], + ); + }, + ); + } else { + Navigator.pop(context); + } + }); + }, + child: const Text("Next"), + ) + ], + ); + }); + } +} diff --git a/lib/screens/welcome_screen.dart b/lib/screens/welcome_screen.dart new file mode 100644 index 0000000..11cc7ed --- /dev/null +++ b/lib/screens/welcome_screen.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_rust_bridge_template/screens/screens.dart'; +import 'package:google_fonts/google_fonts.dart'; + +import '../globals.dart'; + +class WelcomeScreen extends StatefulWidget { + const WelcomeScreen({super.key}); + + @override + State createState() => _WelcomeScreenState(); +} + +class _WelcomeScreenState extends State + with SingleTickerProviderStateMixin { + bool isVisible = false; + late AnimationController _animationController; + late Animation _animation; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 500), + ); + _animation = + Tween(begin: 0.0, end: 1.0).animate(_animationController); + _animationController.forward(); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: Colors.white, + body: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Center( + child: FadeTransition( + opacity: _animation, + child: Text( + "Welcome, $username.", + style: GoogleFonts.nunito( + fontSize: 52, + fontWeight: FontWeight.bold, + color: Colors.black), + ), + ), + ), + const SizedBox(height: 5), + Center( + child: Visibility( + visible: isVisible, + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.6, + child: Text( + "This little application of mine will help you elevate privilege and display your root folder within this app.", + style: GoogleFonts.nunito( + fontSize: 18, + color: const Color(0xFFBDBDBD), + fontWeight: FontWeight.w500), + textAlign: TextAlign.center, + ), + )), + ), + const SizedBox(height: 20), + Center( + child: TextButton( + onPressed: () { + if (!isVisible) { + setState(() { + isVisible = true; + }); + } else { + Navigator.of(context).push(MaterialPageRoute( + builder: ((context) => const SudoCommandPage()))); + } + }, + style: TextButton.styleFrom( + padding: const EdgeInsets.all(25), + shape: const CircleBorder(), + backgroundColor: Colors.lightBlue), + child: Text( + "→", + style: GoogleFonts.nunito( + fontSize: 25, + color: Colors.white, + fontWeight: FontWeight.w600), + ), + ), + ), + const SizedBox(height: 10), + // Center( + // child: RichText( + // text: TextSpan( + // text: "How does it work?", + // style: GoogleFonts.nunito( + // decoration: TextDecoration.underline, + // fontSize: 14, + // color: Colors.blue, + // fontWeight: FontWeight.w600), + // recognizer: TapGestureRecognizer()..onTap = () {}))) + ]), + ); + } +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index cccf817..e777c67 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,8 @@ import FlutterMacOS import Foundation +import path_provider_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) } diff --git a/macos/Runner/bridge_generated.h b/macos/Runner/bridge_generated.h index ae8c386..7b9ac1c 100644 --- a/macos/Runner/bridge_generated.h +++ b/macos/Runner/bridge_generated.h @@ -9,6 +9,11 @@ typedef int64_t DartPort; typedef bool (*DartPostCObjectFnType)(DartPort port_id, void *message); +typedef struct wire_uint_8_list { + uint8_t *ptr; + int32_t len; +} wire_uint_8_list; + typedef struct DartCObject *WireSyncReturn; void store_dart_post_cobject(DartPostCObjectFnType ptr); @@ -21,20 +26,26 @@ uintptr_t new_dart_opaque(Dart_Handle handle); intptr_t init_frb_dart_api_dl(void *obj); -void wire_platform(int64_t port_); +void wire_get_username(int64_t port_); + +void wire_print_root_folder(int64_t port_, struct wire_uint_8_list *password); + +void wire_check_polkit(int64_t port_); -void wire_rust_release_mode(int64_t port_); +struct wire_uint_8_list *new_uint_8_list_0(int32_t len); void free_WireSyncReturn(WireSyncReturn ptr); static int64_t dummy_method_to_enforce_bundling(void) { int64_t dummy_var = 0; - dummy_var ^= ((int64_t) (void*) wire_platform); - dummy_var ^= ((int64_t) (void*) wire_rust_release_mode); + dummy_var ^= ((int64_t) (void*) wire_get_username); + dummy_var ^= ((int64_t) (void*) wire_print_root_folder); + dummy_var ^= ((int64_t) (void*) wire_check_polkit); + dummy_var ^= ((int64_t) (void*) new_uint_8_list_0); dummy_var ^= ((int64_t) (void*) free_WireSyncReturn); dummy_var ^= ((int64_t) (void*) store_dart_post_cobject); dummy_var ^= ((int64_t) (void*) get_dart_object); dummy_var ^= ((int64_t) (void*) drop_dart_object); dummy_var ^= ((int64_t) (void*) new_dart_opaque); return dummy_var; -} \ No newline at end of file +} diff --git a/native/Cargo.toml b/native/Cargo.toml index 667e107..d354462 100644 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -11,3 +11,4 @@ crate-type = ["cdylib", "staticlib"] [dependencies] anyhow = "1" flutter_rust_bridge = "1" +sudo = "0.6.0" diff --git a/native/src/api.rs b/native/src/api.rs index 976bb9d..7c74953 100644 --- a/native/src/api.rs +++ b/native/src/api.rs @@ -1,59 +1,71 @@ -// This is the entry point of your Rust library. -// When adding new code to your project, note that only items used -// here will be transformed to their Dart equivalents. - -// A plain enum without any fields. This is similar to Dart- or C-style enums. -// flutter_rust_bridge is capable of generating code for enums with fields -// (@freezed classes in Dart and tagged unions in C). -pub enum Platform { - Unknown, - Android, - Ios, - Windows, - Unix, - MacIntel, - MacApple, - Wasm, +use std::{process::Command, io::Write}; + +/// Rust function that retrieves the username of the current user by executing a terminal command. +/// It utilizes the underlying operating system's capabilities to obtain the username. +/// Returns a string value. +/// +/// ### Example +/// ```rust +/// fn main() { +/// let username = get_username(); // returns current user's username +/// println!("Hello, {}!", username); +/// } +/// ``` + +pub fn get_username() -> String { + let process = Command::new("whoami").output().expect("failed to execute"); + let username = String::from_utf8(process.stdout) + .expect("failed to convert to string") + .trim() + .to_string(); + + return username; } -// A function definition in Rust. Similar to Dart, the return type must always be named -// and is never inferred. -pub fn platform() -> Platform { - // This is a macro, a special expression that expands into code. In Rust, all macros - // end with an exclamation mark and can be invoked with all kinds of brackets (parentheses, - // brackets and curly braces). However, certain conventions exist, for example the - // vector macro is almost always invoked as vec![..]. - // - // The cfg!() macro returns a boolean value based on the current compiler configuration. - // When attached to expressions (#[cfg(..)] form), they show or hide the expression at compile time. - // Here, however, they evaluate to runtime values, which may or may not be optimized out - // by the compiler. A variety of configurations are demonstrated here which cover most of - // the modern oeprating systems. Try running the Flutter application on different machines - // and see if it matches your expected OS. - // - // Furthermore, in Rust, the last expression in a function is the return value and does - // not have the trailing semicolon. This entire if-else chain forms a single expression. - if cfg!(windows) { - Platform::Windows - } else if cfg!(target_os = "android") { - Platform::Android - } else if cfg!(target_os = "ios") { - Platform::Ios - } else if cfg!(all(target_os = "macos", target_arch = "aarch64")) { - Platform::MacApple - } else if cfg!(target_os = "macos") { - Platform::MacIntel - } else if cfg!(target_family = "wasm") { - Platform::Wasm - } else if cfg!(unix) { - Platform::Unix +/// Rust function that uses the sudo command to elevate privilege. +/// Takes in a password as a String argument to grant access to the root folder. +/// +/// ### Return Values +/// - Returns Some(String) if the function was successfullt executed and the proper rights have been granted. +/// - Returns None if the function fails to execute correctly and the proper rights have not been given to the administrator. +/// +/// ### Example +/// ```rust +/// fn main() { +/// let return_statement = print_root_folder("pass1234".to_string()); +/// println("{:?}", return_statement); // prints the output statement provided the password is correct or throws an error. +/// } +/// ``` +pub fn print_root_folder(password : String) -> Option { + let mut command = Command::new("sudo") + .args(["-k","-S", "ls", "-la", "/root/"]) + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .spawn() + .expect("failed to execute command"); + + let mut input = command.stdin.take().expect("failed to open stdin"); + std::thread::spawn(move || {input.write_all(password.as_bytes()).expect("failed to write to stdin")}); + + let output = command + .wait_with_output() + .expect("failed to wait for child process"); + + if output.status.code() == Some(0) { + return Some(String::from_utf8(output.stdout).expect("failed to convert to string")); } else { - Platform::Unknown + return None; } } -// The convention for Rust identifiers is the snake_case, -// and they are automatically converted to camelCase on the Dart side. -pub fn rust_release_mode() -> bool { - cfg!(not(debug_assertions)) -} +pub fn check_polkit() -> Option { + let command = Command::new("pkexec").spawn().expect("failed to execute command"); + + let output = command.wait_with_output().expect("failed to wait for child process"); + + if output.status.code() == Some(0) { + return Some("Successful".to_string()); + } else { + return None; + } +} \ No newline at end of file diff --git a/native/src/bridge_generated.io.rs b/native/src/bridge_generated.io.rs index a4ec960..bc7df1c 100644 --- a/native/src/bridge_generated.io.rs +++ b/native/src/bridge_generated.io.rs @@ -2,23 +2,59 @@ use super::*; // Section: wire functions #[no_mangle] -pub extern "C" fn wire_platform(port_: i64) { - wire_platform_impl(port_) +pub extern "C" fn wire_get_username(port_: i64) { + wire_get_username_impl(port_) } #[no_mangle] -pub extern "C" fn wire_rust_release_mode(port_: i64) { - wire_rust_release_mode_impl(port_) +pub extern "C" fn wire_print_root_folder(port_: i64, password: *mut wire_uint_8_list) { + wire_print_root_folder_impl(port_, password) +} + +#[no_mangle] +pub extern "C" fn wire_check_polkit(port_: i64) { + wire_check_polkit_impl(port_) } // Section: allocate functions +#[no_mangle] +pub extern "C" fn new_uint_8_list_0(len: i32) -> *mut wire_uint_8_list { + let ans = wire_uint_8_list { + ptr: support::new_leak_vec_ptr(Default::default(), len), + len, + }; + support::new_leak_box_ptr(ans) +} + // Section: related functions // Section: impl Wire2Api +impl Wire2Api for *mut wire_uint_8_list { + fn wire2api(self) -> String { + let vec: Vec = self.wire2api(); + String::from_utf8_lossy(&vec).into_owned() + } +} + +impl Wire2Api> for *mut wire_uint_8_list { + fn wire2api(self) -> Vec { + unsafe { + let wrap = support::box_from_leak_ptr(self); + support::vec_from_leak_ptr(wrap.ptr, wrap.len) + } + } +} // Section: wire structs +#[repr(C)] +#[derive(Clone)] +pub struct wire_uint_8_list { + ptr: *mut u8, + len: i32, +} + // Section: impl NewWithNullPtr pub trait NewWithNullPtr { diff --git a/native/src/bridge_generated.rs b/native/src/bridge_generated.rs index 9f63b66..b7ca9fa 100644 --- a/native/src/bridge_generated.rs +++ b/native/src/bridge_generated.rs @@ -9,7 +9,7 @@ clippy::too_many_arguments )] // AUTO GENERATED FILE, DO NOT EDIT. -// Generated by `flutter_rust_bridge`@ 1.62.1. +// Generated by `flutter_rust_bridge`@ 1.77.1. use crate::api::*; use core::panic::UnwindSafe; @@ -21,24 +21,37 @@ use std::sync::Arc; // Section: wire functions -fn wire_platform_impl(port_: MessagePort) { +fn wire_get_username_impl(port_: MessagePort) { FLUTTER_RUST_BRIDGE_HANDLER.wrap( WrapInfo { - debug_name: "platform", + debug_name: "get_username", port: Some(port_), mode: FfiCallMode::Normal, }, - move || move |task_callback| Ok(platform()), + move || move |task_callback| Ok(get_username()), ) } -fn wire_rust_release_mode_impl(port_: MessagePort) { +fn wire_print_root_folder_impl(port_: MessagePort, password: impl Wire2Api + UnwindSafe) { FLUTTER_RUST_BRIDGE_HANDLER.wrap( WrapInfo { - debug_name: "rust_release_mode", + debug_name: "print_root_folder", port: Some(port_), mode: FfiCallMode::Normal, }, - move || move |task_callback| Ok(rust_release_mode()), + move || { + let api_password = password.wire2api(); + move |task_callback| Ok(print_root_folder(api_password)) + }, + ) +} +fn wire_check_polkit_impl(port_: MessagePort) { + FLUTTER_RUST_BRIDGE_HANDLER.wrap( + WrapInfo { + debug_name: "check_polkit", + port: Some(port_), + mode: FfiCallMode::Normal, + }, + move || move |task_callback| Ok(check_polkit()), ) } // Section: wrapper structs @@ -63,23 +76,15 @@ where (!self.is_null()).then(|| self.wire2api()) } } -// Section: impl IntoDart -impl support::IntoDart for Platform { - fn into_dart(self) -> support::DartAbi { - match self { - Self::Unknown => 0, - Self::Android => 1, - Self::Ios => 2, - Self::Windows => 3, - Self::Unix => 4, - Self::MacIntel => 5, - Self::MacApple => 6, - Self::Wasm => 7, - } - .into_dart() +impl Wire2Api for u8 { + fn wire2api(self) -> u8 { + self } } + +// Section: impl IntoDart + // Section: executor support::lazy_static! { diff --git a/native/src/bridge_generated.web.rs b/native/src/bridge_generated.web.rs index afb4ffc..8d1b622 100644 --- a/native/src/bridge_generated.web.rs +++ b/native/src/bridge_generated.web.rs @@ -2,13 +2,18 @@ use super::*; // Section: wire functions #[wasm_bindgen] -pub fn wire_platform(port_: MessagePort) { - wire_platform_impl(port_) +pub fn wire_get_username(port_: MessagePort) { + wire_get_username_impl(port_) } #[wasm_bindgen] -pub fn wire_rust_release_mode(port_: MessagePort) { - wire_rust_release_mode_impl(port_) +pub fn wire_print_root_folder(port_: MessagePort, password: String) { + wire_print_root_folder_impl(port_, password) +} + +#[wasm_bindgen] +pub fn wire_check_polkit(port_: MessagePort) { + wire_check_polkit_impl(port_) } // Section: allocate functions @@ -17,4 +22,31 @@ pub fn wire_rust_release_mode(port_: MessagePort) { // Section: impl Wire2Api +impl Wire2Api for String { + fn wire2api(self) -> String { + self + } +} + +impl Wire2Api> for Box<[u8]> { + fn wire2api(self) -> Vec { + self.into_vec() + } +} // Section: impl Wire2Api for JsValue + +impl Wire2Api for JsValue { + fn wire2api(self) -> String { + self.as_string().expect("non-UTF-8 string, or not a string") + } +} +impl Wire2Api for JsValue { + fn wire2api(self) -> u8 { + self.unchecked_into_f64() as _ + } +} +impl Wire2Api> for JsValue { + fn wire2api(self) -> Vec { + self.unchecked_into::().to_vec().into() + } +} diff --git a/pubspec.yaml b/pubspec.yaml index e7f3e0b..8d63e78 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: - sdk: ">=2.17.5 <3.0.0" + sdk: ">=3.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -27,20 +27,17 @@ environment: # the latest version available on pub.dev. To see which dependencies have newer # versions available, run `flutter pub outdated`. dependencies: - flutter: - sdk: flutter - - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. + blinking_text: ^1.0.2 cupertino_icons: ^1.0.2 ffi: ^2.0.1 + flutter: + sdk: flutter flutter_rust_bridge: ^1.45.0 + google_fonts: null meta: ^1.8.0 dev_dependencies: - flutter_test: - sdk: flutter + ffigen: ^8.0.2 # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is @@ -48,11 +45,11 @@ dev_dependencies: # package. See that file for information about deactivating specific lint # rules and activating additional ones. flutter_lints: ^2.0.0 - ffigen: ^7.2.4 + flutter_test: + sdk: flutter # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec - # The following section is specific to Flutter packages. flutter: @@ -60,18 +57,14 @@ flutter: # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true - # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg - # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware - # For details regarding adding assets from package dependencies, see # https://flutter.dev/assets-and-images/#from-packages - # To add custom fonts to your application, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a