From ad18b358ce8d30e33e4a21292f297f18fe310ac9 Mon Sep 17 00:00:00 2001 From: Tovi Jaeschke-Rogers Date: Sun, 9 Oct 2022 16:03:40 +1030 Subject: [PATCH] Add permissions view --- Backend/Api/Messages/CreateConversation.go | 12 +- Backend/Database/Seeder/MessageSeeder.go | 31 ++-- Backend/Models/Conversations.go | 3 + Backend/Util/Files.go | 2 +- Backend/attachments/.gitkeep | 0 mobile/lib/components/select_message_ttl.dart | 6 +- mobile/lib/models/conversations.dart | 29 ++- mobile/lib/utils/storage/conversations.dart | 1 - mobile/lib/utils/storage/database.dart | 5 +- .../views/main/conversation/edit_details.dart | 44 +++-- .../views/main/conversation/permissions.dart | 174 ++++++++++++++++++ .../lib/views/main/conversation/settings.dart | 13 +- .../conversation/settings_user_list_item.dart | 6 +- 13 files changed, 281 insertions(+), 45 deletions(-) create mode 100644 Backend/attachments/.gitkeep create mode 100644 mobile/lib/views/main/conversation/permissions.dart diff --git a/Backend/Api/Messages/CreateConversation.go b/Backend/Api/Messages/CreateConversation.go index 12d54e1..3bb62f2 100644 --- a/Backend/Api/Messages/CreateConversation.go +++ b/Backend/Api/Messages/CreateConversation.go @@ -15,6 +15,9 @@ type RawCreateConversationData struct { ID string `json:"id"` Name string `json:"name"` TwoUser string `json:"two_user"` + AdminAddMembers string `json:"admin_add_members"` + AdminEditInfo string `json:"admin_edit_info"` + AdminSendMessages string `json:"admin_send_messages"` Users []Models.ConversationDetailUser `json:"users"` UserConversations []Models.UserConversation `json:"user_conversations"` } @@ -37,9 +40,12 @@ func CreateConversation(w http.ResponseWriter, r *http.Request) { Base: Models.Base{ ID: uuid.FromStringOrNil(rawConversationData.ID), }, - Name: rawConversationData.Name, - TwoUser: rawConversationData.TwoUser, - Users: rawConversationData.Users, + Name: rawConversationData.Name, + TwoUser: rawConversationData.TwoUser, + AdminAddMembers: rawConversationData.AdminAddMembers, + AdminEditInfo: rawConversationData.AdminEditInfo, + AdminSendMessages: rawConversationData.AdminSendMessages, + Users: rawConversationData.Users, } err = Database.CreateConversationDetail(&messageThread) diff --git a/Backend/Database/Seeder/MessageSeeder.go b/Backend/Database/Seeder/MessageSeeder.go index a117742..5ccabad 100644 --- a/Backend/Database/Seeder/MessageSeeder.go +++ b/Backend/Database/Seeder/MessageSeeder.go @@ -93,11 +93,12 @@ func seedMessage( func seedConversationDetail(key AesKey) (Models.ConversationDetail, error) { var ( - messageThread Models.ConversationDetail - name string - nameCiphertext []byte - twoUserCiphertext []byte - err error + conversationDetail Models.ConversationDetail + name string + nameCiphertext []byte + falseCiphertext []byte + trueCiphertext []byte + err error ) name = "Test Conversation" @@ -107,18 +108,26 @@ func seedConversationDetail(key AesKey) (Models.ConversationDetail, error) { panic(err) } - twoUserCiphertext, err = key.AesEncrypt([]byte("false")) + falseCiphertext, err = key.AesEncrypt([]byte("false")) + if err != nil { + panic(err) + } + + trueCiphertext, err = key.AesEncrypt([]byte("true")) if err != nil { panic(err) } - messageThread = Models.ConversationDetail{ - Name: base64.StdEncoding.EncodeToString(nameCiphertext), - TwoUser: base64.StdEncoding.EncodeToString(twoUserCiphertext), + conversationDetail = Models.ConversationDetail{ + Name: base64.StdEncoding.EncodeToString(nameCiphertext), + TwoUser: base64.StdEncoding.EncodeToString(falseCiphertext), + AdminAddMembers: base64.StdEncoding.EncodeToString(trueCiphertext), + AdminEditInfo: base64.StdEncoding.EncodeToString(trueCiphertext), + AdminSendMessages: base64.StdEncoding.EncodeToString(falseCiphertext), } - err = Database.CreateConversationDetail(&messageThread) - return messageThread, err + err = Database.CreateConversationDetail(&conversationDetail) + return conversationDetail, err } func seedUserConversation( diff --git a/Backend/Models/Conversations.go b/Backend/Models/Conversations.go index bc6ff3a..2d048fc 100644 --- a/Backend/Models/Conversations.go +++ b/Backend/Models/Conversations.go @@ -16,6 +16,9 @@ type ConversationDetail struct { Attachment Attachment ` json:"attachment"` MessageExpiryDefault MessageExpiry `gorm:"default:no_expiry" json:"-" sql:"type:ENUM('fifteen_min', 'thirty_min', 'one_hour', 'three_hour', 'six_hour', 'twelve_hour', 'one_day', 'three_day', 'no_expiry')"` // Stored encrypted MessageExpiry string `gorm:"-" json:"message_expiry"` // Stored encrypted + AdminAddMembers string ` json:"admin_add_members"` // Stored encrypted + AdminEditInfo string ` json:"admin_edit_info"` // Stored encrypted + AdminSendMessages string ` json:"admin_send_messages"` // Stored encrypted } // ConversationDetailUser all users associated with a customer diff --git a/Backend/Util/Files.go b/Backend/Util/Files.go index 154b1ef..e802402 100644 --- a/Backend/Util/Files.go +++ b/Backend/Util/Files.go @@ -17,7 +17,7 @@ func WriteFile(contents []byte) (string, error) { fileName = RandomString(32) filePath = fmt.Sprintf( - "/app/attachments/%s", + "./attachments/%s", fileName, ) diff --git a/Backend/attachments/.gitkeep b/Backend/attachments/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/mobile/lib/components/select_message_ttl.dart b/mobile/lib/components/select_message_ttl.dart index c2be882..3252808 100644 --- a/mobile/lib/components/select_message_ttl.dart +++ b/mobile/lib/components/select_message_ttl.dart @@ -58,12 +58,12 @@ class _SelectMessageTTLState extends State { ), body: Padding( padding: const EdgeInsets.only(top: 30), - child: list(), + child: _list(), ), ); } - Widget list() { + Widget _list() { return ListView.builder( itemCount: messageExpiryValues.length, shrinkWrap: true, @@ -84,7 +84,7 @@ class _SelectMessageTTLState extends State { children: [ selectedExpiry == key ? const Icon(Icons.check) : - const SizedBox(width: 20), + const SizedBox(width: 24), const SizedBox(width: 16), Expanded( child: Align( diff --git a/mobile/lib/models/conversations.dart b/mobile/lib/models/conversations.dart index c07683f..9286aae 100644 --- a/mobile/lib/models/conversations.dart +++ b/mobile/lib/models/conversations.dart @@ -37,7 +37,10 @@ Future createConversation(String title, List friends, bool twoUser: twoUser, status: ConversationStatus.pending, isRead: true, - messageExpiryDefault: 'no_expiry' + messageExpiryDefault: 'no_expiry', + adminAddMembers: true, + adminEditInfo: true, + adminSendMessages: false, ); await db.insert( @@ -163,6 +166,9 @@ Future getConversationById(String id) async { isRead: maps[0]['is_read'] == 1, icon: file, messageExpiryDefault: maps[0]['message_expiry'], + adminAddMembers: maps[0]['admin_add_members'] == 1, + adminEditInfo: maps[0]['admin_edit_info'] == 1, + adminSendMessages: maps[0]['admin_send_messages'] == 1, ); } @@ -193,6 +199,9 @@ Future> getConversations() async { isRead: maps[i]['is_read'] == 1, icon: file, messageExpiryDefault: maps[i]['message_expiry'] ?? 'no_expiry', + adminAddMembers: maps[i]['admin_add_members'] == 1, + adminEditInfo: maps[i]['admin_edit_info'] == 1, + adminSendMessages: maps[i]['admin_send_messages'] == 1, ); }); } @@ -227,6 +236,9 @@ Future getTwoUserConversation(String userId) async { status: ConversationStatus.values[maps[0]['status']], isRead: maps[0]['is_read'] == 1, messageExpiryDefault: maps[0]['message_expiry'], + adminAddMembers: maps[0]['admin_add_members'] == 1, + adminEditInfo: maps[0]['admin_edit_info'] == 1, + adminSendMessages: maps[0]['admin_send_messages'] == 1, ); } @@ -241,6 +253,9 @@ class Conversation { ConversationStatus status; bool isRead; String messageExpiryDefault = 'no_expiry'; + bool adminAddMembers = true; + bool adminEditInfo = true; + bool adminSendMessages = false; File? icon; Conversation({ @@ -253,6 +268,9 @@ class Conversation { required this.status, required this.isRead, required this.messageExpiryDefault, + required this.adminAddMembers, + required this.adminEditInfo, + required this.adminSendMessages, this.icon, }); @@ -283,6 +301,9 @@ class Conversation { status: ConversationStatus.complete, isRead: true, messageExpiryDefault: 'no_expiry', + adminAddMembers: true, + adminEditInfo: true, + adminSendMessages: false, ); } @@ -329,6 +350,9 @@ class Conversation { 'users': await getEncryptedConversationUsers(this, symKey), 'two_user': AesHelper.aesEncrypt(symKey, Uint8List.fromList((twoUser ? 'true' : 'false').codeUnits)), 'message_expiry': messageExpiryDefault, + 'admin_add_members': AesHelper.aesEncrypt(symKey, Uint8List.fromList((adminAddMembers ? 'true' : 'false').codeUnits)), + 'admin_edit_info': AesHelper.aesEncrypt(symKey, Uint8List.fromList((adminEditInfo ? 'true' : 'false').codeUnits)), + 'admin_send_messages': AesHelper.aesEncrypt(symKey, Uint8List.fromList((adminSendMessages ? 'true' : 'false').codeUnits)), 'user_conversations': userConversations, }; @@ -367,6 +391,9 @@ class Conversation { 'is_read': isRead ? 1 : 0, 'file': icon != null ? icon!.path : null, 'message_expiry': messageExpiryDefault, + 'admin_add_members': adminAddMembers ? 1 : 0, + 'admin_edit_info': adminEditInfo ? 1 : 0, + 'admin_send_messages': adminSendMessages ? 1 : 0, }; } diff --git a/mobile/lib/utils/storage/conversations.dart b/mobile/lib/utils/storage/conversations.dart index 198d26b..44a3148 100644 --- a/mobile/lib/utils/storage/conversations.dart +++ b/mobile/lib/utils/storage/conversations.dart @@ -3,7 +3,6 @@ import 'dart:convert'; import 'package:Capsule/exceptions/update_data_exception.dart'; import 'package:Capsule/utils/storage/get_file.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:http/http.dart' as http; import 'package:pointycastle/export.dart'; import 'package:sqflite/sqflite.dart'; diff --git a/mobile/lib/utils/storage/database.dart b/mobile/lib/utils/storage/database.dart index 7670074..f95b865 100644 --- a/mobile/lib/utils/storage/database.dart +++ b/mobile/lib/utils/storage/database.dart @@ -41,7 +41,10 @@ Future getDatabaseConnection() async { status INTEGER, is_read INTEGER, file TEXT, - message_expiry TEXT + message_expiry TEXT, + admin_add_members INTEGER, + admin_edit_info INTEGER, + admin_send_messages INTEGER ); '''); diff --git a/mobile/lib/views/main/conversation/edit_details.dart b/mobile/lib/views/main/conversation/edit_details.dart index 5ec0606..e197498 100644 --- a/mobile/lib/views/main/conversation/edit_details.dart +++ b/mobile/lib/views/main/conversation/edit_details.dart @@ -125,25 +125,31 @@ class _ConversationEditDetails extends State { const SizedBox(height: 20), - showFileSelector ? - Padding( - padding: const EdgeInsets.only(bottom: 10), - child: FilePicker( - cameraHandle: (XFile image) { - setState(() { - conversationIcon = File(image.path); - showFileSelector = false; - }); - }, - galleryHandleSingle: (XFile image) async { - setState(() { - conversationIcon = File(image.path); - showFileSelector = false; - }); - }, - ), - ) : - const SizedBox(height: 10), + AnimatedSwitcher( + duration: const Duration(milliseconds: 250), + transitionBuilder: (Widget child, Animation animation) { + return SizeTransition(sizeFactor: animation, child: child); + }, + child: showFileSelector ? + Padding( + padding: const EdgeInsets.only(bottom: 10), + child: FilePicker( + cameraHandle: (XFile image) { + setState(() { + conversationIcon = File(image.path); + showFileSelector = false; + }); + }, + galleryHandleSingle: (XFile image) async { + setState(() { + conversationIcon = File(image.path); + showFileSelector = false; + }); + }, + ), + ) : + const SizedBox(height: 10), + ), TextFormField( controller: conversationNameController, diff --git a/mobile/lib/views/main/conversation/permissions.dart b/mobile/lib/views/main/conversation/permissions.dart new file mode 100644 index 0000000..16304db --- /dev/null +++ b/mobile/lib/views/main/conversation/permissions.dart @@ -0,0 +1,174 @@ +import 'package:Capsule/components/flash_message.dart'; +import 'package:Capsule/exceptions/update_data_exception.dart'; +import 'package:Capsule/utils/storage/conversations.dart'; +import 'package:Capsule/utils/storage/database.dart'; +import 'package:flutter/material.dart'; + +import '/components/custom_title_bar.dart'; +import '/models/conversations.dart'; + +class ConversationPermissions extends StatefulWidget { + const ConversationPermissions({ + Key? key, + required this.conversation, + }) : super(key: key); + + final Conversation conversation; + + @override + _ConversationPermissionsState createState() => _ConversationPermissionsState(); +} + +class _ConversationPermissionsState extends State { + Map> perms = { + 'admin_add_members': { + 'title': 'Add Members', + 'desc': 'Restrict adding members to admins', + }, + 'admin_edit_info': { + 'title': 'Edit Info', + 'desc': 'Restrict editing the conversation information to admins', + }, + 'admin_send_messages': { + 'title': 'Send Messages', + 'desc': 'Restrict sending messages to admins', + }, + }; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: CustomTitleBar( + title: const Text( + 'Permissions', + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold + ) + ), + beforeBack: () async { + final db = await getDatabaseConnection(); + db.update( + 'conversations', + widget.conversation.toMap(), + where: 'id = ?', + whereArgs: [widget.conversation.id], + ); + + updateConversation(widget.conversation) + .catchError((error) { + String message = error.toString(); + if (error.runtimeType != UpdateDataException) { + message = 'An error occured, please try again later'; + } + + showMessage(message, context); + }); + }, + showBack: true, + backgroundColor: Colors.transparent, + ), + body: Padding( + padding: const EdgeInsets.only(top: 30), + child: _list(), + ), + ); + } + + Widget _list() { + return ListView.builder( + itemCount: perms.length, + shrinkWrap: true, + itemBuilder: (context, i) { + String key = perms.keys.elementAt(i); + + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () { + _setValue(key); + }, + + child: Padding( + padding: const EdgeInsets.only(left: 30, right: 20, top: 8, bottom: 8), + child: Row( + children: [ + _getValue(key) ? + const Icon(Icons.check) : + const SizedBox(width: 24), + const SizedBox(width: 16), + Expanded( + child: Align( + alignment: Alignment.centerLeft, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + perms[key]!['title'] ?? '', + textAlign: TextAlign.left, + style: const TextStyle( + fontSize: 20, + fontWeight: FontWeight.normal, + ), + ), + const SizedBox(height: 5), + Text( + perms[key]!['desc'] ?? '', + textAlign: TextAlign.left, + style: const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w200, + ), + ), + ] + ) + ) + ) + ], + ) + ) + ); + } + ); + } + + bool _getValue(String key) { + switch (key) { + case 'admin_add_members': { + return widget.conversation.adminAddMembers; + } + case 'admin_edit_info': { + return widget.conversation.adminEditInfo; + } + case 'admin_send_messages': { + return widget.conversation.adminSendMessages; + } + default: { + return false; + } + } + } + + void _setValue(String key) { + switch (key) { + case 'admin_add_members': { + setState(() { + widget.conversation.adminAddMembers = !widget.conversation.adminAddMembers; + }); + break; + } + case 'admin_edit_info': { + setState(() { + widget.conversation.adminEditInfo = !widget.conversation.adminEditInfo; + }); + break; + } + case 'admin_send_messages': { + setState(() { + widget.conversation.adminSendMessages = !widget.conversation.adminSendMessages; + }); + break; + } + } + } +} + diff --git a/mobile/lib/views/main/conversation/settings.dart b/mobile/lib/views/main/conversation/settings.dart index 9aacc23..c891820 100644 --- a/mobile/lib/views/main/conversation/settings.dart +++ b/mobile/lib/views/main/conversation/settings.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:Capsule/utils/storage/session_cookie.dart'; +import 'package:Capsule/views/main/conversation/permissions.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart' as http; @@ -23,12 +24,13 @@ import '/utils/storage/database.dart'; import '/utils/storage/conversations.dart'; class ConversationSettings extends StatefulWidget { - final Conversation conversation; const ConversationSettings({ Key? key, required this.conversation, }) : super(key: key); + final Conversation conversation; + @override State createState() => _ConversationSettingsState(); } @@ -101,7 +103,7 @@ class _ConversationSettingsState extends State { ), ), - widget.conversation.admin && !widget.conversation.twoUser ? IconButton( + (widget.conversation.admin && widget.conversation.adminEditInfo) && !widget.conversation.twoUser ? IconButton( iconSize: 20, icon: const Icon(Icons.edit), padding: const EdgeInsets.all(5.0), @@ -303,7 +305,11 @@ return Theme.of(context).colorScheme.onBackground; ) ), onPressed: () { - print('Permissions'); + Navigator.of(context).push( + MaterialPageRoute(builder: (context) => ConversationPermissions( + conversation: widget.conversation, + )) + ); } ), ], @@ -321,6 +327,7 @@ return Theme.of(context).colorScheme.onBackground; return ConversationSettingsUserListItem( user: users[i], isAdmin: widget.conversation.admin, + twoUser: widget.conversation.twoUser, profile: profile!, // TODO: Fix this ); } diff --git a/mobile/lib/views/main/conversation/settings_user_list_item.dart b/mobile/lib/views/main/conversation/settings_user_list_item.dart index 446702b..5be6a6b 100644 --- a/mobile/lib/views/main/conversation/settings_user_list_item.dart +++ b/mobile/lib/views/main/conversation/settings_user_list_item.dart @@ -7,11 +7,13 @@ import '/models/my_profile.dart'; class ConversationSettingsUserListItem extends StatefulWidget{ final ConversationUser user; final bool isAdmin; + final bool twoUser; final MyProfile profile; const ConversationSettingsUserListItem({ Key? key, required this.user, required this.isAdmin, + required this.twoUser, required this.profile, }) : super(key: key); @@ -22,7 +24,7 @@ class ConversationSettingsUserListItem extends StatefulWidget{ class _ConversationSettingsUserListItemState extends State { Widget admin() { - if (!widget.user.admin) { + if (!widget.user.admin || widget.twoUser) { return const SizedBox.shrink(); } @@ -42,7 +44,7 @@ class _ConversationSettingsUserListItemState extends State