Skip to content

Commit 981bade

Browse files
authored
Merge pull request #1603 from byquanton/feature/nextcloud-login-flow-v2
Use Nextcloud Login Flow for login
2 parents 3e3872a + 8a989ec commit 981bade

File tree

5 files changed

+127
-77
lines changed

5 files changed

+127
-77
lines changed

core/Constants.vala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
*/
2121

2222
namespace Constants {
23+
public const string SOUP_USER_AGENT = "Planify";
2324
public const string TODOIST_CLIENT_ID = "b0dd7d3714314b1dbbdab9ee03b6b432";
2425
public const string TODOIST_CLIENT_SECRET = "a86dfeb12139459da3e5e2a8c197c678";
2526
public const string TODOIST_SCOPE = "data:read_write,data:delete,project:delete";

core/Services/CalDAV/Providers/Nextcloud.vala

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
*/
2121

2222
public class Services.CalDAV.Providers.Nextcloud : Services.CalDAV.Providers.Base {
23+
24+
private Soup.Session session;
25+
private Json.Parser parser;
26+
27+
2328
// vala-lint=naming-convention
2429
public static string GET_SYNC_TOKEN_REQUEST = """
2530
<x0:propfind xmlns:x0="DAV:">
@@ -111,6 +116,9 @@ public class Services.CalDAV.Providers.Nextcloud : Services.CalDAV.Providers.Bas
111116
""";
112117
113118
public Nextcloud () {
119+
session = new Soup.Session ();
120+
parser = new Json.Parser ();
121+
114122
LOGIN_REQUEST = """
115123
<d:propfind xmlns:d="DAV:">
116124
<d:prop>
@@ -272,7 +280,7 @@ public class Services.CalDAV.Providers.Nextcloud : Services.CalDAV.Providers.Bas
272280
""";
273281
}
274282
275-
public override string get_server_url (string url, string username, string password) {
283+
private string get_real_server_url (string url) {
276284
string server_url = "";
277285
278286
try {
@@ -293,6 +301,91 @@ public class Services.CalDAV.Providers.Nextcloud : Services.CalDAV.Providers.Bas
293301
debug (e.message);
294302
}
295303

304+
return server_url;
305+
}
306+
307+
308+
public async HttpResponse start_login_flow (string server_url, GLib.Cancellable cancellable) {
309+
HttpResponse response = new HttpResponse ();
310+
311+
string login_url = "%s/index.php/login/v2".printf (get_real_server_url (server_url));
312+
313+
var message = new Soup.Message ("POST", login_url);
314+
message.request_headers.append ("User-Agent", Constants.SOUP_USER_AGENT); // The User Agent is used by Nextcloud for the App Name
315+
message.set_request_body_from_bytes ("application/json", new Bytes (LOGIN_REQUEST.data));
316+
317+
try {
318+
GLib.Bytes stream = yield session.send_and_read_async (message, GLib.Priority.HIGH, cancellable);
319+
320+
parser.load_from_data ((string) stream.get_data ());
321+
322+
var root = parser.get_root ().get_object ();
323+
324+
var login_link = root.get_string_member ("login");
325+
326+
var poll = root.get_object_member ("poll");
327+
var poll_token = poll.get_string_member ("token");
328+
var poll_endpoint = poll.get_string_member ("endpoint");
329+
330+
AppInfo.launch_default_for_uri (login_link, null);
331+
332+
int timeout = 20 * 60;
333+
int interval = 5;
334+
335+
while (timeout > 0 && !cancellable.is_cancelled ()) {
336+
var poll_msg = new Soup.Message ("POST", poll_endpoint);
337+
338+
poll_msg.request_headers.append ("User-Agent", Constants.SOUP_USER_AGENT);
339+
poll_msg.set_request_body_from_bytes ("application/json", new Bytes ("""
340+
{ "token": "%s" }
341+
""".printf (poll_token).data));
342+
343+
try {
344+
GLib.Bytes poll_response = yield session.send_and_read_async (poll_msg, GLib.Priority.HIGH, cancellable);
345+
var poll_str = (string) poll_response.get_data ();
346+
347+
Json.Parser poll_parser = new Json.Parser ();
348+
poll_parser.load_from_data (poll_str);
349+
350+
var poll_root = poll_parser.get_root ();
351+
352+
if (poll_root.get_node_type () == Json.NodeType.OBJECT) {
353+
var poll_object = poll_root.get_object ();
354+
355+
if (poll_object.has_member ("loginName")) {
356+
357+
var server = poll_object.get_string_member ("server"); // From now on we use the provided server url and not the one the user supplied
358+
var login_name = poll_object.get_string_member ("loginName");
359+
var app_password = poll_object.get_string_member ("appPassword");
360+
361+
var login_response = yield Core.get_default ().login (CalDAVType.NEXTCLOUD, server, login_name, app_password, cancellable);
362+
363+
return login_response;
364+
}
365+
}
366+
367+
} catch (Error err) {
368+
response.error_code = err.code;
369+
response.error = "Polling error: %s".printf (err.message);
370+
break;
371+
}
372+
373+
yield Util.nap (interval * 1000);
374+
375+
timeout -= interval;
376+
}
377+
378+
}catch (Error e) {
379+
response.error_code = e.code;
380+
response.error = "login error: %s".printf (e.message);
381+
}
382+
383+
return response;
384+
}
385+
386+
public override string get_server_url (string url, string username, string password) {
387+
string server_url = get_real_server_url (url);
388+
296389
return "%s/remote.php/dav".printf (server_url);
297390
}
298391

Lines changed: 7 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
public class Services.CalDAV.Providers.Base {
1+
public abstract class Services.CalDAV.Providers.Base {
22
// vala-lint=naming-convention
33
public virtual string LOGIN_REQUEST { get; set; default = ""; }
44

@@ -7,27 +7,15 @@ public class Services.CalDAV.Providers.Base {
77

88
public virtual string TASKLIST_REQUEST { get; set; default = ""; }
99

10-
public virtual string get_server_url (string server_url, string username, string password) {
11-
return "";
12-
}
10+
public abstract string get_server_url (string server_url, string username, string password);
1311

14-
public virtual string get_account_url (string server_url, string username) {
15-
return "";
16-
}
12+
public abstract string get_account_url (string server_url, string username);
1713

18-
public virtual void set_user_data (GXml.DomDocument doc, Objects.Source source) {
14+
public abstract void set_user_data (GXml.DomDocument doc, Objects.Source source);
1915

20-
}
16+
public abstract string get_all_taskslist_url (string server_url, string username);
2117

22-
public virtual string get_all_taskslist_url (string server_url, string username) {
23-
return "";
24-
}
18+
public abstract Gee.ArrayList<Objects.Project> get_projects_by_doc (GXml.DomDocument doc, Objects.Source source);
2519

26-
public virtual Gee.ArrayList<Objects.Project> get_projects_by_doc (GXml.DomDocument doc, Objects.Source source) {
27-
return new Gee.ArrayList<Objects.Project> ();
28-
}
29-
30-
public virtual bool is_vtodo_calendar (GXml.DomElement element) {
31-
return false;
32-
}
20+
public abstract bool is_vtodo_calendar (GXml.DomElement element);
3321
}

core/Util/Util.vala

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1311,6 +1311,15 @@ We hope you’ll enjoy using Planify!""");
13111311

13121312
return return_value;
13131313
}
1314+
1315+
// https://wiki.gnome.org/Projects/Vala/AsyncSamples#Async_sleep_example
1316+
public static async void nap (uint interval, int priority = GLib.Priority.DEFAULT) {
1317+
GLib.Timeout.add (interval, () => {
1318+
nap.callback ();
1319+
return false;
1320+
}, priority);
1321+
yield;
1322+
}
13141323
}
13151324

13161325
public class RegexMarkdown {

src/Dialogs/Preferences/PreferencesWindow.vala

Lines changed: 16 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -861,12 +861,12 @@ public class Dialogs.Preferences.PreferencesWindow : Adw.PreferencesDialog {
861861
var settings_header = new Dialogs.Preferences.SettingsHeader (_("Accounts"));
862862

863863
var todoist_item = new Widgets.ContextMenu.MenuItem (_("Todoist"));
864-
var caldav_item = new Widgets.ContextMenu.MenuItem (_("Nextcloud"));
864+
var nextcloud_item = new Widgets.ContextMenu.MenuItem (_("Nextcloud"));
865865

866866
var menu_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0);
867867
menu_box.margin_top = menu_box.margin_bottom = 3;
868868
menu_box.append (todoist_item);
869-
menu_box.append (caldav_item);
869+
menu_box.append (nextcloud_item);
870870

871871
var popover = new Gtk.Popover () {
872872
has_arrow = true,
@@ -953,8 +953,8 @@ public class Dialogs.Preferences.PreferencesWindow : Adw.PreferencesDialog {
953953
push_subpage (get_oauth_todoist_page ());
954954
});
955955

956-
caldav_item.clicked.connect (() => {
957-
push_subpage (get_caldav_setup_page ());
956+
nextcloud_item.clicked.connect (() => {
957+
push_subpage (get_nextcloud_setup_page ());
958958
});
959959

960960
return new Adw.NavigationPage (toolbar_view, "account");
@@ -1328,36 +1328,19 @@ public class Dialogs.Preferences.PreferencesWindow : Adw.PreferencesDialog {
13281328
return page;
13291329
}
13301330

1331-
private Adw.NavigationPage get_caldav_setup_page () {
1331+
private Adw.NavigationPage get_nextcloud_setup_page () {
13321332
var settings_header = new Dialogs.Preferences.SettingsHeader (_("Nextcloud Setup"));
13331333

13341334
var server_entry = new Adw.EntryRow ();
13351335
server_entry.title = _("Server URL");
13361336

1337-
var username_entry = new Adw.EntryRow ();
1338-
username_entry.title = _("User Name");
1339-
1340-
var password_entry = new Adw.PasswordEntryRow ();
1341-
password_entry.title = _("Password");
1342-
1343-
var providers_model = new Gtk.StringList (null);
1344-
providers_model.append (_("Nextcloud"));
1345-
// providers_model.append (_("Radicale"));
1346-
1347-
var providers_row = new Adw.ComboRow ();
1348-
providers_row.title = _("Provider");
1349-
providers_row.model = providers_model;
1350-
13511337
var entries_group = new Adw.PreferencesGroup ();
13521338

13531339
entries_group.add (server_entry);
1354-
entries_group.add (username_entry);
1355-
entries_group.add (password_entry);
1356-
entries_group.add (providers_row);
13571340

13581341
var message_label = new Gtk.Label ("""Server URL examples:
1359-
- https://evi.nl.tab.digital/
1360-
- https://use01.thegood.cloud/""") {
1342+
- https://cloud.example.com/
1343+
- https://example.com/nextcloud/""") {
13611344
wrap = true
13621345
};
13631346

@@ -1433,35 +1416,7 @@ public class Dialogs.Preferences.PreferencesWindow : Adw.PreferencesDialog {
14331416
server_entry.remove_css_class ("error");
14341417
}
14351418

1436-
if (server_entry.has_css_class ("error") || username_entry.has_css_class ("error") | password_entry.has_css_class ("error")) {
1437-
login_button.sensitive = false;
1438-
} else {
1439-
login_button.sensitive = true;
1440-
}
1441-
});
1442-
1443-
username_entry.changed.connect (() => {
1444-
if (username_entry.text != null && username_entry.text != "") {
1445-
username_entry.remove_css_class ("error");
1446-
} else {
1447-
username_entry.add_css_class ("error");
1448-
}
1449-
1450-
if (server_entry.has_css_class ("error") || username_entry.has_css_class ("error") | password_entry.has_css_class ("error")) {
1451-
login_button.sensitive = false;
1452-
} else {
1453-
login_button.sensitive = true;
1454-
}
1455-
});
1456-
1457-
password_entry.changed.connect (() => {
1458-
if (password_entry.text != null && password_entry.text != "") {
1459-
password_entry.remove_css_class ("error");
1460-
} else {
1461-
password_entry.add_css_class ("error");
1462-
}
1463-
1464-
if (server_entry.has_css_class ("error") || username_entry.has_css_class ("error") | password_entry.has_css_class ("error")) {
1419+
if (server_entry.has_css_class ("error")) {
14651420
login_button.sensitive = false;
14661421
} else {
14671422
login_button.sensitive = true;
@@ -1477,12 +1432,16 @@ public class Dialogs.Preferences.PreferencesWindow : Adw.PreferencesDialog {
14771432
cancellable.cancel ();
14781433
});
14791434

1480-
Services.CalDAV.Core.get_default ().login.begin (CalDAVType.parse_index (providers_row.selected), server_entry.text, username_entry.text, password_entry.text, cancellable, (obj, res) => {
1481-
HttpResponse response = Services.CalDAV.Core.get_default ().login.end (res);
1435+
var core_service = Services.CalDAV.Core.get_default ();
1436+
var nextcloud_provider = (Services.CalDAV.Providers.Nextcloud) core_service.providers_map.get (CalDAVType.NEXTCLOUD.to_string ());
1437+
1438+
nextcloud_provider.start_login_flow.begin (server_entry.text, cancellable, (obj, res) => {
1439+
HttpResponse response = nextcloud_provider.start_login_flow.end (res);
1440+
14821441
if (response.status) {
14831442
Objects.Source source = (Objects.Source) response.data_object.get_object ();
1484-
Services.CalDAV.Core.get_default ().add_caldav_account.begin (source, cancellable, (obj, res) => {
1485-
response = Services.CalDAV.Core.get_default ().add_caldav_account.end (res);
1443+
core_service.add_caldav_account.begin (source, cancellable, (obj, res) => {
1444+
response = core_service.add_caldav_account.end (res);
14861445

14871446
if (!response.status) {
14881447
login_button.is_loading = false;

0 commit comments

Comments
 (0)