From 6d16ee798863a7f196c14915c47888adf0758f1a Mon Sep 17 00:00:00 2001 From: smizrahi Date: Mon, 18 Sep 2023 14:45:17 +0200 Subject: [PATCH] feat(core): Add mobile provisioning download for Apple's devices --- SoObjects/SOGo/GNUmakefile | 8 +- SoObjects/SOGo/SOGoMobileProvision.h | 39 ++++++ SoObjects/SOGo/SOGoMobileProvision.m | 125 ++++++++++++++++++ UI/Contacts/English.lproj/Localizable.strings | 1 + UI/Contacts/French.lproj/Localizable.strings | 1 + UI/Contacts/UIxContactView.m | 27 ++++ UI/Contacts/product.plist | 5 + .../English.lproj/Localizable.strings | 1 + UI/Scheduler/French.lproj/Localizable.strings | 1 + UI/Scheduler/UIxCalView.m | 26 ++++ UI/Scheduler/product.plist | 5 + .../ContactsUI/UIxContactFoldersView.wox | 5 + UI/Templates/SchedulerUI/UIxCalMainView.wox | 6 + .../js/Contacts/AddressBook.service.js | 17 +++ .../js/Scheduler/Calendar.service.js | 17 +++ 15 files changed, 282 insertions(+), 2 deletions(-) create mode 100644 SoObjects/SOGo/SOGoMobileProvision.h create mode 100644 SoObjects/SOGo/SOGoMobileProvision.m diff --git a/SoObjects/SOGo/GNUmakefile b/SoObjects/SOGo/GNUmakefile index 6db87bb37..fcb78def3 100644 --- a/SoObjects/SOGo/GNUmakefile +++ b/SoObjects/SOGo/GNUmakefile @@ -93,7 +93,9 @@ SOGo_HEADER_FILES = \ \ JWT.h \ \ - NGMimeBodyPart+SOGo.h + NGMimeBodyPart+SOGo.h \ + \ + SOGoMobileProvision.h all:: @touch SOGoBuild.m @@ -180,7 +182,9 @@ SOGo_OBJC_FILES = \ \ JWT.m \ \ - NGMimeBodyPart+SOGo.m + NGMimeBodyPart+SOGo.m \ + \ + SOGoMobileProvision.m SOGo_C_FILES += lmhash.c aes.c crypt_blowfish.c pkcs5_pbkdf2.c diff --git a/SoObjects/SOGo/SOGoMobileProvision.h b/SoObjects/SOGo/SOGoMobileProvision.h new file mode 100644 index 000000000..f4f931235 --- /dev/null +++ b/SoObjects/SOGo/SOGoMobileProvision.h @@ -0,0 +1,39 @@ +/* SOGoMobileProvision.h - this file is part of SOGo + * + * Copyright (C) 2023 Alinto + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#ifndef SOGOMOBILEPROVISION_H +#define SOGOMOBILEPROVISION_H + +#include + +@class SOGoMobileProvision; +@class NSString; + +@interface SOGoMobileProvision : SOGoObject +{ + +} + ++ (NSString *)plistForCalendarsWithContext:(WOContext *)context andPath:(NSString *)path; ++ (NSString *)plistForContactsWithContext:(WOContext *)context andPath:(NSString *)path; + +@end + +#endif /* SOGOMOBILEPROVISION_H */ diff --git a/SoObjects/SOGo/SOGoMobileProvision.m b/SoObjects/SOGo/SOGoMobileProvision.m new file mode 100644 index 000000000..f9b53aa9b --- /dev/null +++ b/SoObjects/SOGo/SOGoMobileProvision.m @@ -0,0 +1,125 @@ + +/* SOGoMobileProvision.m - this file is part of SOGo + * + * Copyright (C) 2023 Alinto + * + * This file is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This file 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ + +#import + +#import "SOGoMobileProvision.h" +#import "SOGoUser.h" + +typedef NS_ENUM(NSInteger, ProvisioningType) { + ProvisioningTypeCalendar, + ProvisioningTypeContact +}; + +@implementation SOGoMobileProvision + ++ (NSString *)_plistWithContext:(WOContext *)context andPath:(NSString *)path andType:(ProvisioningType) provisioningType +{ + NSData *plistData; + SOGoUser *activeUser; + NSURL *serverURL; + NSDictionary *provisioning; + NSError *error; + NSString *payloadType, *prefix, *type; + + activeUser = [context activeUser]; + serverURL = [context serverURL]; + + if (!context || !path) { + [self errorWithFormat: @"Invalid provisioning profile - no parameters"]; + return nil; + } + + switch(provisioningType) { + case ProvisioningTypeCalendar: + payloadType = @"com.apple.caldav.account"; + prefix = @"CalDAV"; + type = @"Calendar"; + break; + case ProvisioningTypeContact: + payloadType = @"com.apple.carddav.account"; + prefix = @"CardDAV"; + type = @"Contact"; + break; + } + + provisioning = [NSDictionary dictionaryWithObjectsAndKeys: + [NSArray arrayWithObject: [NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithFormat:@"%@ %@", type, [activeUser login]], [NSString stringWithFormat:@"%@%@", prefix, @"AccountDescription"], + [serverURL host], [NSString stringWithFormat:@"%@%@", prefix, @"HostName"], + [serverURL port], [NSString stringWithFormat:@"%@%@", prefix, @"Port"], + path, [NSString stringWithFormat:@"%@%@", prefix, @"PrincipalURL"], + [NSNumber numberWithBool:[[serverURL scheme] isEqualToString:@"https"]], [NSString stringWithFormat:@"%@%@", prefix, @"UseSSL"], + [activeUser login], [NSString stringWithFormat:@"%@%@", prefix, @"Username"], + [NSString stringWithFormat: @"SOGo %@ provisioning", prefix], @"PayloadDescription", + [NSString stringWithFormat:@"%@ %@", type, [activeUser login]], @"PayloadDisplayName", + [NSString stringWithFormat:@"%@.apple.%@", [serverURL host], [prefix lowercaseString]], @"PayloadIdentifier", + [serverURL host], @"PayloadOrganization", + payloadType, @"PayloadType", + [SOGoObject globallyUniqueObjectId], @"PayloadUUID", + [NSNumber numberWithInt: 1], @"PayloadVersion", + nil]], @"PayloadContent", + [NSString stringWithFormat:@"SOGo %@ provisioning", prefix], @"PayloadDescription", + [NSString stringWithFormat: @"%@ (%@)", [activeUser login], type], @"PayloadDisplayName", + [NSString stringWithFormat:@"%@.%@.apple", [prefix lowercaseString], [serverURL host]], @"PayloadIdentifier", + @"SOGo", @"PayloadOrganization", + [NSNumber numberWithBool: NO], @"PayloadRemovalDisallowed", + @"Configuration", @"PayloadType", + [SOGoObject globallyUniqueObjectId], @"PayloadUUID", + [NSNumber numberWithInt: 1], @"PayloadVersion", + nil]; + + return [NSPropertyListSerialization dataWithPropertyList: provisioning + format: NSPropertyListXMLFormat_v1_0 + options: 0 + error: &error]; +} + ++ (NSString *)plistForCalendarsWithContext:(WOContext *)context andPath:(NSString *)path +{ + NSData *plistData; + SOGoUser *activeUser; + + plistData = [self _plistWithContext: context andPath: path andType: ProvisioningTypeCalendar]; + if (nil != plistData) { + return [[[NSString alloc] initWithData:plistData encoding:NSUTF8StringEncoding] autorelease]; + } else { + [self errorWithFormat: @"Invalid provisioning profile plist for account : %@", [activeUser login]]; + return nil; + } +} + ++ (NSString *)plistForContactsWithContext:(WOContext *)context andPath:(NSString *)path +{ + NSData *plistData; + SOGoUser *activeUser; + + plistData = [self _plistWithContext: context andPath: path andType: ProvisioningTypeContact]; + if (nil != plistData) { + return [[[NSString alloc] initWithData:plistData encoding:NSUTF8StringEncoding] autorelease]; + } else { + [self errorWithFormat: @"Invalid provisioning profile plist for account : %@", [activeUser login]]; + return nil; + } +} + + +@end diff --git a/UI/Contacts/English.lproj/Localizable.strings b/UI/Contacts/English.lproj/Localizable.strings index 8c97bd106..05874f86b 100644 --- a/UI/Contacts/English.lproj/Localizable.strings +++ b/UI/Contacts/English.lproj/Localizable.strings @@ -193,6 +193,7 @@ "Work" = "Work"; "Mobile" = "Mobile"; "Pager" = "Pager"; +"Download configuration profile" = "Download configuration profile"; /* categories */ "contacts_category_labels" = "Colleague, Competitor, Customer, Friend, Family, Business Partner, Provider, Press, VIP"; diff --git a/UI/Contacts/French.lproj/Localizable.strings b/UI/Contacts/French.lproj/Localizable.strings index c0fd58562..c8e548558 100644 --- a/UI/Contacts/French.lproj/Localizable.strings +++ b/UI/Contacts/French.lproj/Localizable.strings @@ -193,6 +193,7 @@ "Work" = "Travail"; "Mobile" = "Portable"; "Pager" = "Téléavertisseur"; +"Download configuration profile" = "Télécharger le profil de configuration"; /* categories */ "contacts_category_labels" = "Ami, Client, Collègue, Concurrent, Famille, Fournisseur, Partenaire d'affaire, Presse, VIP"; diff --git a/UI/Contacts/UIxContactView.m b/UI/Contacts/UIxContactView.m index 8de22fdd4..f4e6408c8 100644 --- a/UI/Contacts/UIxContactView.m +++ b/UI/Contacts/UIxContactView.m @@ -35,9 +35,11 @@ #import #import #import +#import #import #import +#import #import "UIxContactView.h" @@ -448,4 +450,29 @@ return [NSString stringWithFormat: @"%@/photo", [soURL absoluteString]]; } +- (WOResponse *) mobileconfigAction +{ + SOGoContactFolders *folders; + NSString *davURL, *plistContent, *disposition; + WOResponse *response; + + folders = [self clientObject]; + davURL = [[folders container] davURLAsString]; + plistContent = [SOGoMobileProvision plistForContactsWithContext: context andPath: davURL]; + + if (nil != plistContent) { + response = [self responseWithStatus: 200 + andString: plistContent]; + [response setHeader: @"application/x-plist; charset=utf-8" + forKey: @"content-type"]; + disposition = [NSString stringWithString: @"attachment; filename=\"contacts.mobileconfig\""]; + [response setHeader: disposition forKey: @"Content-Disposition"]; + } else { + response = [self responseWithStatus: 500 + andString: @"Error while generating profile"]; + } + + return response; +} + @end /* UIxContactView */ diff --git a/UI/Contacts/product.plist b/UI/Contacts/product.plist index 6455b527d..4406e6fa2 100644 --- a/UI/Contacts/product.plist +++ b/UI/Contacts/product.plist @@ -50,6 +50,11 @@ pageName = "UIxContactFoldersView"; actionName = "saveDragHandleState"; }; + mobileconfig = { + protectedBy = "Access Contents Information"; + actionClass = "UIxContactView"; + actionName = "mobileconfig"; + }; }; }; diff --git a/UI/Scheduler/English.lproj/Localizable.strings b/UI/Scheduler/English.lproj/Localizable.strings index a503b3d05..667ded54e 100644 --- a/UI/Scheduler/English.lproj/Localizable.strings +++ b/UI/Scheduler/English.lproj/Localizable.strings @@ -65,6 +65,7 @@ "A total of %{0} events were imported in the calendar." = "A total of %{0} events were imported in the calendar."; "Compose E-Mail to All Attendees" = "Compose E-Mail to All Attendees"; "Compose E-Mail to Undecided Attendees" = "Compose E-Mail to Undecided Attendees"; +"Download configuration profile" = "Download configuration profile"; /* Relative dates */ "Yesterday" = "Yesterday"; diff --git a/UI/Scheduler/French.lproj/Localizable.strings b/UI/Scheduler/French.lproj/Localizable.strings index 17c023992..881456885 100644 --- a/UI/Scheduler/French.lproj/Localizable.strings +++ b/UI/Scheduler/French.lproj/Localizable.strings @@ -65,6 +65,7 @@ "A total of %{0} events were imported in the calendar." = "Un total de %{0} événements ont été importés dans le calendrier."; "Compose E-Mail to All Attendees" = "Rédiger un courriel pour tous les participants"; "Compose E-Mail to Undecided Attendees" = "Rédiger un courriel pour les participants indécis"; +"Download configuration profile" = "Télécharger le profil de configuration"; /* Relative dates */ "Yesterday" = "Hier"; diff --git a/UI/Scheduler/UIxCalView.m b/UI/Scheduler/UIxCalView.m index b6265249e..a52b2f55a 100644 --- a/UI/Scheduler/UIxCalView.m +++ b/UI/Scheduler/UIxCalView.m @@ -36,6 +36,7 @@ #import #import #import +#import #import @@ -657,4 +658,29 @@ return r; } +- (WOResponse *) mobileconfigAction +{ + SOGoAppointmentFolders *folders; + NSString *davURL, *plistContent, *disposition; + WOResponse *response; + + folders = [self clientObject]; + davURL = [[folders container] davURLAsString]; + plistContent = [SOGoMobileProvision plistForCalendarsWithContext: context andPath: davURL]; + + if (nil != plistContent) { + response = [self responseWithStatus: 200 + andString: plistContent]; + [response setHeader: @"application/x-plist; charset=utf-8" + forKey: @"content-type"]; + disposition = [NSString stringWithString: @"attachment; filename=\"calendar.mobileconfig\""]; + [response setHeader: disposition forKey: @"Content-Disposition"]; + } else { + response = [self responseWithStatus: 500 + andString: @"Error while generating profile"]; + } + + return response; +} + @end /* UIxCalView */ diff --git a/UI/Scheduler/product.plist b/UI/Scheduler/product.plist index 81898bd85..526197ef4 100644 --- a/UI/Scheduler/product.plist +++ b/UI/Scheduler/product.plist @@ -137,6 +137,11 @@ protectedBy = "View"; pageName = "UIxReminderEditor"; }; + mobileconfig = { + protectedBy = "Access Contents Information"; + actionClass = "UIxCalView"; + actionName = "mobileconfig"; + }; }; }; diff --git a/UI/Templates/ContactsUI/UIxContactFoldersView.wox b/UI/Templates/ContactsUI/UIxContactFoldersView.wox index e73ec47aa..94df7d08f 100644 --- a/UI/Templates/ContactsUI/UIxContactFoldersView.wox +++ b/UI/Templates/ContactsUI/UIxContactFoldersView.wox @@ -117,6 +117,11 @@ + + +  + + diff --git a/UI/Templates/SchedulerUI/UIxCalMainView.wox b/UI/Templates/SchedulerUI/UIxCalMainView.wox index 7478befa2..201c7967c 100644 --- a/UI/Templates/SchedulerUI/UIxCalMainView.wox +++ b/UI/Templates/SchedulerUI/UIxCalMainView.wox @@ -595,6 +595,12 @@ + + + +  + + diff --git a/UI/WebServerResources/js/Contacts/AddressBook.service.js b/UI/WebServerResources/js/Contacts/AddressBook.service.js index dddf058fd..749ab052c 100644 --- a/UI/WebServerResources/js/Contacts/AddressBook.service.js +++ b/UI/WebServerResources/js/Contacts/AddressBook.service.js @@ -744,6 +744,23 @@ } }; + /** + * @function downloadProvisioningProfile + * @memberof Calendar.prototype + * @desc Download provisioning profile for Apple's devices + * @returns a promise of the HTTP operation + */ + AddressBook.prototype.downloadProvisioningProfile = function () { + var options; + + options = { + type: 'application/octet-stream', + filename: 'addressbook.mobileconfig' + } + + return AddressBook.$$resource.open('', 'mobileconfig', null, options); + }; + /** * @function $unwrap * @memberof AddressBook.prototype diff --git a/UI/WebServerResources/js/Scheduler/Calendar.service.js b/UI/WebServerResources/js/Scheduler/Calendar.service.js index 2f31847f2..cf30e3f89 100644 --- a/UI/WebServerResources/js/Scheduler/Calendar.service.js +++ b/UI/WebServerResources/js/Scheduler/Calendar.service.js @@ -576,6 +576,23 @@ return resource.open(path, 'export', null, options); }; + /** + * @function downloadProvisioningProfile + * @memberof Calendar.prototype + * @desc Download provisioning profile for Apple's devices + * @returns a promise of the HTTP operation + */ + Calendar.prototype.downloadProvisioningProfile = function () { + var options; + + options = { + type: 'application/octet-stream', + filename: 'calendar.mobileconfig' + } + + return Calendar.$$resource.open('', 'mobileconfig', null, options); + }; + /** * @function $setActivation * @memberof Calendar.prototype