Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b864c55a8a | ||
|
|
9017c85581 | ||
|
|
5cba10ccff | ||
|
|
b48704e7ff | ||
|
|
69116d8eb7 | ||
|
|
d15f0ee804 | ||
|
|
a1e8a5c4af | ||
|
|
02dd5c3501 | ||
|
|
7a7f85d1a7 | ||
|
|
6d4016dfdb | ||
|
|
e06a0d2035 | ||
|
|
f8ff98b657 | ||
|
|
512f14f6d0 | ||
|
|
9f4f48a44f | ||
|
|
092e7694e0 | ||
|
|
42c227beca | ||
|
|
c400f45ef1 | ||
|
|
78655ae127 | ||
|
|
29e00f3e9e | ||
|
|
beb0d9cb1b | ||
|
|
5c1278a895 | ||
|
|
616c9291e0 | ||
|
|
9941e2a04b | ||
|
|
b40d777a86 | ||
|
|
70f72eb92f | ||
|
|
0a828a739e | ||
|
|
395a01ed52 | ||
|
|
c05b331ac0 | ||
|
|
da32b3f86d | ||
|
|
a09720cf22 | ||
|
|
0364f7f2a4 | ||
|
|
00082eb0bc | ||
|
|
178b1a3a0e | ||
|
|
9ffa72ee9b | ||
|
|
169a0c6125 | ||
|
|
c7b0133000 | ||
|
|
2f9c2cf4da | ||
|
|
57db6b5f91 | ||
|
|
f65ab9b43a | ||
|
|
2258d5cd60 | ||
|
|
ea427ea41a | ||
|
|
ce86d14826 | ||
|
|
7f7c743976 | ||
|
|
6f86506b28 | ||
|
|
e44bc1fc91 | ||
|
|
c90e2b0501 | ||
|
|
cd0324dd49 | ||
|
|
581b2c1912 | ||
|
|
9a9d968756 | ||
|
|
fe003eeef5 | ||
|
|
a09f4d8f49 | ||
|
|
617c1bbc64 | ||
|
|
5bab72726a | ||
|
|
01863987dd | ||
|
|
f1257088f2 | ||
|
|
1c611a0a4a | ||
|
|
f2ba6870d5 | ||
|
|
253d14cfce | ||
|
|
b3c77a0e00 |
No files matched your search
@@ -437,8 +437,8 @@ Defaults to `300`.
|
||||
|
||||
|S |SOGoAuthenticationType
|
||||
|Parameter used to define the way by which users will be authenticated.
|
||||
For C.A.S., specify `cas`. For SAML2, specify `saml2`. For anything
|
||||
else, leave that value empty.
|
||||
For C.A.S., specify `cas`. For SAML2, specify `saml2`. For opendID Connect, specify `openid`.
|
||||
For anything else, leave that value empty.
|
||||
|
||||
|S |SOGoTrustProxyAuthentication
|
||||
|Parameter used to set whether HTTP username should be trusted.
|
||||
@@ -477,6 +477,19 @@ example.
|
||||
|
||||
Defaults to `YES` when unset.
|
||||
|
||||
|S |SOGoCalendarEnableJitsiLink
|
||||
|Parameter allowing users to create Jitsi link when creating an event.
|
||||
Be aware that a least one of the attendees will need an account from a
|
||||
platform that Jitsi supports: Google, Facebook or Github
|
||||
|
||||
Defaults to `NO` when unset.
|
||||
|
||||
|D |SOGoCalendarJitsiBaseUrl
|
||||
|Only used if SOGoCalendarEnableJitsiLink is set to YES. String for the URL of
|
||||
the jitsi server used for creating the meeting link.
|
||||
|
||||
Defaults to `https://meet.jit.si` when unset.
|
||||
|
||||
|S |SOGoSAML2PrivateKeyLocation
|
||||
|The location of the SSL private key file on the filesystem that is used
|
||||
by SOGo to sign and encrypt communications with the SAML2 identity
|
||||
@@ -815,6 +828,8 @@ Obiously, if your users can connect without specifying a domain, let this parame
|
||||
|S |SOGoDomainAllowed
|
||||
|Parameter used to define which domains SOGo should allowed during a connection request. This parameter is an array of strings.
|
||||
|
||||
|S |SOGoURLCreateAccount
|
||||
|Add a link below the login form and set the link using the value of this setting.
|
||||
|
||||
|S |SOGoPasswordRecoveryEnabled
|
||||
|Boolean enable password recovery with secret question or secondary e-mail. Default value is `NO`.
|
||||
@@ -1544,6 +1559,81 @@ SOGoIMAPCASServiceName should be set to the actual imap service name
|
||||
expected by pam_cas, otherwise it will fail to authenticate incoming
|
||||
connection properly.
|
||||
|
||||
|
||||
Authenticating using OPENID
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
SOGo natively supports OPENID authentication. For activating OpenId authentication you need first to make sure that
|
||||
_SOGoAuthenticationType_ is set to `openid`,
|
||||
_SOGoXSRFValidationEnabled_ is set to `NO` and set the following parameters:
|
||||
|
||||
[cols="^4,46,50a"]
|
||||
|=======================================================================
|
||||
|S |OCSOpenIdURL
|
||||
|Parameter used to set the database URL for openID session.
|
||||
|
||||
For MariaDB/MySQL, set the database URL to something like: mysql://sogo:sogo@127.0.0.1:3306/sogo/sogo_openid.
|
||||
|
||||
|
||||
|S |SOGoOpenIdConfigUrl
|
||||
|Parameter used to specify the endpoint of OpenID Provider Configuration, mandatory. For example:
|
||||
https://myopenid.net/.well-known/openid-configuration
|
||||
|
||||
|S |SOGoOpenIdClient
|
||||
|Name of your openid client, mandatory.
|
||||
|
||||
|S |SOGoOpenIdClientSecret
|
||||
|Secret of your openid client, mandatory
|
||||
|
||||
|S |SOGoOpenIdScope
|
||||
|Scope or your openid client, mandatory. List of words space separated like this:
|
||||
"openid profile email"
|
||||
|
||||
|S |SOGoOpenIdEmailParam
|
||||
|Name of the parameter from user profile that contains the mail/uid.
|
||||
|
||||
Defaults to `email` when unset.
|
||||
|
||||
|S |SOGoOpenIdEnableRefreshToken
|
||||
|Set to `YES` to Enable the mechanism of refresh token if provided. You may have to configure
|
||||
and/or add a value to your scope for it to work.
|
||||
|
||||
Defaults to `NO` when unset.
|
||||
|
||||
|S |SOGoOpenIdTokenCheckInterval
|
||||
|Number of seconds before sogo check again the user's access token validity.
|
||||
This is to prevent sogo to do too much request to the openid server.
|
||||
|
||||
Defaults to `0` when unset.
|
||||
|
||||
|S |SOGoOpenIdLogoutEnabled
|
||||
|Allow user to end their openId with the webmail. Meaning that will disconnect them from
|
||||
the others applicaitons as well.
|
||||
|
||||
Defaults to `NO` when unset.
|
||||
|=======================================================================
|
||||
|
||||
|
||||
The tricky part shows up for the imap and smtp sever. SOGo doesn't know the password
|
||||
of the user and only have its access token. A new auth mechanism has been implemented,
|
||||
the https://developers.google.com/gmail/imap/xoauth2-protocol#initial_client_response[xoauth2]
|
||||
|
||||
You can set it with the parameter _NGImap4AuthMechanism_ and/or _SOGoSMTPAuthenticationType_
|
||||
|
||||
*With dovecot:* +
|
||||
Dovecot natively supports xoauth2 and can be figured as such: https://doc.dovecot.org/2.3/configuration_manual/authentication/oauth2/
|
||||
|
||||
*With cyrus:* +
|
||||
Cyrus doesn't support xoauth2 mechanism and pluggins or homemade solutions must be found. +
|
||||
_Please note, as Alinto uses dovecot, we didn't investigate cyrus' case. If one member of the community
|
||||
finds a solution, we will be happy to update this documentation._
|
||||
|
||||
As you can see, a new database table is used for handling openid session. The table is automaticcaly created when _OCSOpenIdURL_ is set.
|
||||
If the user quits the webmail without logging out or trough another application,
|
||||
the session will stays in the table and be useless. That's why a new sogo-tool command has been added to clean this table.
|
||||
You can put it in a cron to do that periodicly. +
|
||||
See _<<sogo-tool-clean-openid-sessions,sogo-tool clean-openid-sessions>>_.
|
||||
|
||||
Authenticating using SAML2
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -1676,6 +1766,9 @@ URL could be set to something like:
|
||||
|
||||
See the "EMail reminders" section in this document for more information.
|
||||
|
||||
|S |SOGoEnableMailCleaning
|
||||
|Parameter used to enable cleaning folders and mailbox (remove mail older than X days). Default value is `NO`.
|
||||
|
||||
|S |SOGoDisableOrganizerEventCheck
|
||||
|Parameter used to disable organizer's calendar event check
|
||||
|
||||
@@ -2095,8 +2188,7 @@ To disable TLS verification for localhost domains, add
|
||||
|
||||
|D |SOGoSMTPAuthenticationType
|
||||
|Activate SMTP authentication and specifies which type is in use.
|
||||
Current, only `PLAIN` is supported and other values will cause
|
||||
the authentication to fail.
|
||||
Current, Are supported `PLAIN` and 'xoauth2' for openid.
|
||||
|
||||
|D |SOGoSMTPMasterUserEnabled
|
||||
|Enable specific SMTP user account for system e-mails (notifications, reminders, ...). Default is `NO`.
|
||||
@@ -2279,6 +2371,7 @@ SASL mechanism. Using `AUTHENTICATE` instead of `LOGIN` is also necessary
|
||||
to enable UTF-8 characters in users' passwords. To enable simple use of
|
||||
`AUTHENTICATE` for this purpose, set this setting to `plain`. Please note
|
||||
that this feature might be limited at this time.
|
||||
Now support `xoauth2` mechanism when using openid. Be sure you imap server undesrtands this mechanism.
|
||||
|
||||
|D |NGImap4ConnectionGroupIdPrefix
|
||||
|Prefix to prepend to names in IMAP ACL transactions, to indicate the
|
||||
@@ -2425,6 +2518,16 @@ appended to the user's vacation message. For example:
|
||||
|
||||
See `SOGoVacationHeaderTemplateFile` for available template variables.
|
||||
|
||||
|D |SOGoVacationAllowZeroDays
|
||||
|Parameter used to enable an option of 0 for the vacation days setting.
|
||||
A value of 0 means that auto response mails are sent for every incoming mail.
|
||||
|
||||
This option has to be supported by the IMAP host
|
||||
(see https://doc.dovecot.org/2.3/settings/pigeonhole-ext/vacation/#pigeonhole_setting-sieve_vacation_min_period
|
||||
for instructions regarding Dovecot/Pigeonhole).
|
||||
|
||||
Defaults to `NO` when unset.
|
||||
|
||||
|D |SOGoForwardEnabled
|
||||
|Parameter used to activate the edition from the preferences window of a
|
||||
forwarding email address. Requires Sieve script support on the IMAP
|
||||
@@ -3602,6 +3705,22 @@ sogo-tool checkup user1
|
||||
sogo-tool checkup -d user1
|
||||
----
|
||||
|
||||
|
||||
[[sogo-tool-clean-openid-sessions]]
|
||||
|
||||
sogo-tool clean-openid-sessions
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Obviously only usefull if you have set SOGo with openId authentication.
|
||||
Will clean all expired openId sessions from the database.
|
||||
|
||||
sogo-tool clean-openid-sessions
|
||||
|
||||
Example:
|
||||
----
|
||||
sogo-tool clean-openid-sessions
|
||||
----
|
||||
|
||||
sogo-tool cleanup
|
||||
~~~~~~~~~~~~~~~~~
|
||||
|
||||
@@ -3949,7 +4068,7 @@ _Defaults_ stores all parameters that can be found in Preferences panel. So it g
|
||||
autoreply to many others options.
|
||||
|
||||
|
||||
user-preferences get|set|unset defaults|settings user key [value|-f filename] [-p credentialFile]
|
||||
user-preferences get|set|unset defaults|settings user key [value|-f filename] [-p credentialFile [-F]]
|
||||
|
||||
* First argument is the action *get*, *set* or *unset*
|
||||
* Second argument is parameter type *settings* or *defaults*
|
||||
@@ -3957,7 +4076,8 @@ autoreply to many others options.
|
||||
* Fourth argument *key* is the name of the parameters where to make the action
|
||||
* If the action is *set*, you should provide either *value* as fifth argument or a file with the value inside with *-f* filename/path
|
||||
* If the action is *set* or *unset* and the *key* concerns a sieve script (filter, vacation, forward...), you
|
||||
should provide a credential file, a one-line file that contains "username:password" of an admin account of your imap/sieve server.
|
||||
should provide a credential file, a one-line file that contains "username:password" of an admin account of your imap/sieve server. If you
|
||||
want t oforce the sieve script activation add -F after.
|
||||
|
||||
As there is a lot of parameters, this documentation will not go into details for each one. To know what are
|
||||
the key names and their value you should got to your database in table defined by _SOGoProfileURL_ in your sogo.conf.
|
||||
@@ -3986,7 +4106,7 @@ sogo-tool user-preferences unset defaults user1 Forward -p /etc/sogo/cred
|
||||
|
||||
*set* action:
|
||||
----
|
||||
sogo-tool user-preferences set defaults user1 Forward '{"forwardAddress":["sogo-tests2@sogo.alinto"],"enabled":1,"keepCopy":0}' -p cred
|
||||
sogo-tool user-preferences set defaults user1 Forward '{"forwardAddress":["sogo-tests2@sogo.alinto"],"enabled":1,"keepCopy":0}' -p cred -F
|
||||
----
|
||||
or create a json file which contains _{"forwardAddress":["sogo-tests2@sogo.alinto"],"enabled":1,"keepCopy":0}_ then:
|
||||
----
|
||||
|
||||
+21
-13
@@ -288,16 +288,16 @@ static BOOL debugLeaks;
|
||||
|
||||
e = [urlStrings objectEnumerator];
|
||||
while (ok && (tmp = [e nextObject]))
|
||||
{
|
||||
value = [defaults stringForKey: tmp];
|
||||
if (value)
|
||||
[self _checkTableWithCM: cm tableURL: value andType: tmp];
|
||||
else
|
||||
{
|
||||
value = [defaults stringForKey: tmp];
|
||||
if (value)
|
||||
[self _checkTableWithCM: cm tableURL: value andType: tmp];
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"No value specified for '%@'", tmp];
|
||||
ok = NO;
|
||||
}
|
||||
[self errorWithFormat: @"No value specified for '%@'", tmp];
|
||||
ok = NO;
|
||||
}
|
||||
}
|
||||
|
||||
if (combined)
|
||||
{
|
||||
@@ -322,9 +322,15 @@ static BOOL debugLeaks;
|
||||
|
||||
// Create the email alarms table, if required
|
||||
if ([defaults enableEMailAlarms])
|
||||
{
|
||||
[[fm alarmsFolder] createFolderIfNotExists];
|
||||
}
|
||||
{
|
||||
[[fm alarmsFolder] createFolderIfNotExists];
|
||||
}
|
||||
|
||||
//Create mandatory openId table, if used
|
||||
if([defaults hasOpenIdType])
|
||||
{
|
||||
[[fm openIdFolder] createFolderIfNotExists];
|
||||
}
|
||||
}
|
||||
|
||||
return ok;
|
||||
@@ -374,8 +380,10 @@ static BOOL debugLeaks;
|
||||
|
||||
request = [_ctx request];
|
||||
login = [SOGoUser getDecryptedUsernameIfNeeded: _key request: request];
|
||||
|
||||
user = [SOGoUser userWithLogin: login roles: nil];
|
||||
if([login isEqualToString: @"changeLanguage"])
|
||||
user = nil;
|
||||
else
|
||||
user = [SOGoUser userWithLogin: login roles: nil];
|
||||
if (user)
|
||||
userFolder = [$(@"SOGoUserFolder") objectWithName: login
|
||||
inContainer: self];
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
*/
|
||||
|
||||
@class NSString, NSArray, NSURL, NSDictionary, NSException;
|
||||
@class GCSChannelManager, GCSAlarmsFolder, GCSAdminFolder, GCSFolder, GCSFolderType, GCSSessionsFolder;
|
||||
@class GCSChannelManager, GCSAlarmsFolder, GCSAdminFolder, GCSFolder, GCSFolderType, GCSSessionsFolder, GCSOpenIdFolder;
|
||||
|
||||
@interface GCSFolderManager : NSObject
|
||||
{
|
||||
@@ -92,6 +92,9 @@
|
||||
/* admin */
|
||||
- (GCSAdminFolder *)adminFolder;
|
||||
|
||||
/* openid */
|
||||
- (GCSOpenIdFolder *) openIdFolder;
|
||||
|
||||
/* folder types */
|
||||
|
||||
- (GCSFolderType *)folderTypeWithName:(NSString *)_name;
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
#import "EOAdaptorChannel+GCS.h"
|
||||
#import "GCSAlarmsFolder.h"
|
||||
#import "GCSAdminFolder.h"
|
||||
#import "GCSOpenIdFolder.h"
|
||||
#import "GCSFolder.h"
|
||||
#import "GCSFolderType.h"
|
||||
#import "GCSSessionsFolder.h"
|
||||
@@ -504,6 +505,12 @@ static BOOL _singleStoreMode = NO;
|
||||
return [GCSAdminFolder adminFolderWithFolderManager: self];
|
||||
}
|
||||
|
||||
/* openId */
|
||||
- (GCSOpenIdFolder *) openIdFolder
|
||||
{
|
||||
return [GCSOpenIdFolder openIdFolderWithFolderManager: self];
|
||||
}
|
||||
|
||||
- (NSString *)generateSQLWhereForInternalNames:(NSArray *)_names
|
||||
exactMatch:(BOOL)_beExact orDirectSubfolderMatch:(BOOL)_directSubs
|
||||
{
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
/* GCSAdminFolder.h - this file is part of $PROJECT_NAME_HERE$
|
||||
*
|
||||
* 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 GCSOPENIDFOLDER_H
|
||||
#define GCSOPENIDOLDER_H
|
||||
|
||||
|
||||
@class NSCalendarDate;
|
||||
@class NSException;
|
||||
@class NSNumber;
|
||||
@class NSString;
|
||||
|
||||
@class GCSFolderManager;
|
||||
|
||||
@interface GCSOpenIdFolder : NSObject
|
||||
{
|
||||
GCSFolderManager *folderManager;
|
||||
}
|
||||
|
||||
+ (id)openIdFolderWithFolderManager:(GCSFolderManager *)newFolderManager;
|
||||
|
||||
- (void) setFolderManager: (GCSFolderManager *) newFolderManager;
|
||||
|
||||
/* operations */
|
||||
|
||||
- (void) createFolderIfNotExists;
|
||||
- (BOOL) canConnectStore;
|
||||
|
||||
- (NSString *) getRefreshToken: (NSString *) _user_session useOldSession: (BOOL) use_old_session;
|
||||
- (NSString *) getNewToken: (NSString *) _old_session;
|
||||
- (NSException *) writeOpenIdSession: (NSString *) _user_session
|
||||
withOldSession: (NSString *) _old_session
|
||||
withRefreshToken: (NSString *) _refresh_token
|
||||
withExpire: (NSNumber *) _expire
|
||||
withRefreshExpire: (NSNumber *) _refresh_expire;
|
||||
- (NSException *) deleteOpenIdSessionFor: (NSString *) _user_session;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* GCSALARMSFOLDER_H */
|
||||
@@ -0,0 +1,363 @@
|
||||
/* GCSAdminFolder.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 <NGExtensions/NSObject+Logs.h>
|
||||
#import <NGExtensions/NSNull+misc.h>
|
||||
|
||||
#import <GDLAccess/EOAdaptorContext.h>
|
||||
#import <GDLAccess/EOAttribute.h>
|
||||
#import <GDLAccess/EOEntity.h>
|
||||
#import <GDLAccess/EOSQLQualifier.h>
|
||||
|
||||
#import "EOQualifier+GCS.h"
|
||||
#import "GCSChannelManager.h"
|
||||
#import "GCSFolderManager.h"
|
||||
#import "GCSSpecialQueries.h"
|
||||
#import "NSURL+GCS.h"
|
||||
|
||||
#import "GCSOpenIdFolder.h"
|
||||
|
||||
static NSString *openIdFolderURLString = nil;
|
||||
|
||||
#warning GCSOpenIdFolder should share a common ancestor with GCSFolder
|
||||
|
||||
@implementation GCSOpenIdFolder
|
||||
|
||||
+ (void) initialize
|
||||
{
|
||||
NSUserDefaults *ud;
|
||||
|
||||
if (!openIdFolderURLString)
|
||||
{
|
||||
ud = [NSUserDefaults standardUserDefaults];
|
||||
ASSIGN (openIdFolderURLString, [ud stringForKey: @"OCSOpenIdURL"]);
|
||||
}
|
||||
}
|
||||
|
||||
+ (id) openIdFolderWithFolderManager: (GCSFolderManager *) newFolderManager
|
||||
{
|
||||
GCSOpenIdFolder *newFolder;
|
||||
|
||||
if (openIdFolderURLString)
|
||||
{
|
||||
newFolder = [self new];
|
||||
[newFolder autorelease];
|
||||
[newFolder setFolderManager: newFolderManager];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"'OCSOpenIdURL' is not set"];
|
||||
newFolder = nil;
|
||||
}
|
||||
|
||||
return newFolder;
|
||||
}
|
||||
|
||||
- (void) setFolderManager: (GCSFolderManager *) newFolderManager
|
||||
{
|
||||
ASSIGN (folderManager, newFolderManager);
|
||||
}
|
||||
|
||||
/* accessors */
|
||||
|
||||
- (NSURL *) _location
|
||||
{
|
||||
NSURL *location;
|
||||
|
||||
if (openIdFolderURLString)
|
||||
location = [NSURL URLWithString: openIdFolderURLString];
|
||||
else
|
||||
{
|
||||
[self warnWithFormat: @"'OCSOpenIdURL' is not set"];
|
||||
location = nil;
|
||||
}
|
||||
|
||||
return location;
|
||||
}
|
||||
|
||||
- (GCSChannelManager *) _channelManager
|
||||
{
|
||||
return [folderManager channelManager];
|
||||
}
|
||||
|
||||
- (NSString *) _storeTableName
|
||||
{
|
||||
return [[self _location] gcsTableName];
|
||||
}
|
||||
|
||||
- (EOEntity *) _storeTableEntityForChannel: (EOAdaptorChannel *) tc
|
||||
{
|
||||
static EOEntity *entity = nil;
|
||||
EOAttribute *attribute;
|
||||
NSString *tableName;
|
||||
NSString *columns[] = {@"c_user_session", @"c_old_session", @"c_session_started", @"c_refresh_token", @"c_access_token_expires_in", @"c_refresh_token_expires_in", nil };
|
||||
NSString **column;
|
||||
NSMutableArray *keys;
|
||||
NSDictionary *types;
|
||||
|
||||
if (!entity)
|
||||
{
|
||||
entity = [EOEntity new];
|
||||
tableName = [self _storeTableName];
|
||||
[entity setName: tableName];
|
||||
[entity setExternalName: tableName];
|
||||
|
||||
types = [[tc specialQueries] openIdAttributeTypes];
|
||||
|
||||
column = columns;
|
||||
while (*column)
|
||||
{
|
||||
attribute = [EOAttribute new];
|
||||
[attribute setName: *column];
|
||||
[attribute setColumnName: *column];
|
||||
[attribute setExternalType: [types objectForKey: *column]];
|
||||
[entity addAttribute: attribute];
|
||||
[attribute release];
|
||||
column++;
|
||||
}
|
||||
|
||||
keys = [NSMutableArray arrayWithCapacity: 1];
|
||||
[keys addObject: [entity attributeNamed: @"c_user_session"]];
|
||||
[entity setPrimaryKeyAttributes: keys];
|
||||
|
||||
keys = [NSMutableArray arrayWithCapacity: 5];
|
||||
[keys addObject: [entity attributeNamed: @"c_old_session"]];
|
||||
[keys addObject: [entity attributeNamed: @"c_session_started"]];
|
||||
[keys addObject: [entity attributeNamed: @"c_refresh_token"]];
|
||||
[keys addObject: [entity attributeNamed: @"c_access_token_expires_in"]];
|
||||
[keys addObject: [entity attributeNamed: @"c_refresh_token_expires_in"]];
|
||||
[entity setClassProperties: keys];
|
||||
[entity setAttributesUsedForLocking: [NSArray array]];
|
||||
}
|
||||
|
||||
return entity;
|
||||
}
|
||||
|
||||
/* connection */
|
||||
|
||||
- (EOAdaptorChannel *) _acquireStoreChannel
|
||||
{
|
||||
return [[self _channelManager] acquireOpenChannelForURL: [self _location]];
|
||||
}
|
||||
|
||||
- (void) _releaseChannel: (EOAdaptorChannel *) _channel
|
||||
{
|
||||
[[self _channelManager] releaseChannel:_channel immediately: YES];
|
||||
}
|
||||
|
||||
- (BOOL) canConnectStore
|
||||
{
|
||||
return [[self _channelManager] canConnect:[self _location]];
|
||||
}
|
||||
|
||||
- (void) createFolderIfNotExists
|
||||
{
|
||||
EOAdaptorChannel *tc;
|
||||
NSString *sql, *tableName;
|
||||
GCSSpecialQueries *queries;
|
||||
|
||||
tc = [self _acquireStoreChannel];
|
||||
tableName = [self _storeTableName];
|
||||
|
||||
queries = [tc specialQueries];
|
||||
|
||||
sql = [NSString stringWithFormat: @"SELECT 1 FROM %@ WHERE 1 = 2", tableName];
|
||||
if ([tc evaluateExpressionX: sql])
|
||||
{
|
||||
sql = [queries createOpenIdFolderWithName: tableName];
|
||||
if (![tc evaluateExpressionX: sql])
|
||||
[self logWithFormat: @"openid folder table '%@' successfully created!", tableName];
|
||||
}
|
||||
else
|
||||
[tc cancelFetch];
|
||||
|
||||
[self _releaseChannel: tc];
|
||||
}
|
||||
|
||||
/* operations */
|
||||
|
||||
/* table has the following fields:
|
||||
c_user_session varchar(255) NOT NULL,
|
||||
c_session_started int(11) NOT NULL,
|
||||
c_refresh_token varchar(4096) DEFAULT '',
|
||||
c_access_token_expires_in int(11) NOT NULL,
|
||||
c_refresh_token_expires_in int(11) DEFAULT NULL,
|
||||
*/
|
||||
|
||||
- (NSDictionary *) recordForSession: (NSString *) _user_session useOldSession: (BOOL) use_old_session
|
||||
{
|
||||
EOAdaptorChannel *tc;
|
||||
EOAdaptorContext *context;
|
||||
NSException *error;
|
||||
NSArray *attrs;
|
||||
NSDictionary *record;
|
||||
EOEntity *entity;
|
||||
EOSQLQualifier *qualifier;
|
||||
|
||||
record = nil;
|
||||
|
||||
tc = [self _acquireStoreChannel];
|
||||
if (tc)
|
||||
{
|
||||
context = [tc adaptorContext];
|
||||
entity = [self _storeTableEntityForChannel: tc];
|
||||
if(!use_old_session)
|
||||
qualifier = [[EOSQLQualifier alloc] initWithEntity: entity
|
||||
qualifierFormat: @"c_user_session='%@'", _user_session];
|
||||
else
|
||||
qualifier = [[EOSQLQualifier alloc] initWithEntity: entity
|
||||
qualifierFormat: @"c_old_session='%@'", _user_session];
|
||||
[qualifier autorelease];
|
||||
|
||||
[context beginTransaction];
|
||||
error = [tc selectAttributesX: [entity attributesUsedForFetch]
|
||||
describedByQualifier: qualifier
|
||||
fetchOrder: nil
|
||||
lock: NO];
|
||||
if (error)
|
||||
[self errorWithFormat:@"%s: cannot execute fetch: %@", __PRETTY_FUNCTION__, error];
|
||||
else
|
||||
{
|
||||
attrs = [tc describeResults: NO];
|
||||
record = [tc fetchAttributes: attrs withZone: NULL];
|
||||
[tc cancelFetch];
|
||||
}
|
||||
[context rollbackTransaction];
|
||||
[self _releaseChannel: tc];
|
||||
}
|
||||
|
||||
return record;
|
||||
}
|
||||
|
||||
- (NSString *) getRefreshToken: (NSString *) _user_session
|
||||
{
|
||||
NSDictionary *r;
|
||||
|
||||
r = [self recordForSession: _user_session useOldSession: NO];
|
||||
if (r && [r objectForKey:@"c_refresh_token"])
|
||||
return [r objectForKey:@"c_refresh_token"];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *) getNewToken: (NSString *) _old_session
|
||||
{
|
||||
NSDictionary *r;
|
||||
r = [self recordForSession: _old_session useOldSession: YES];
|
||||
if (r && [r objectForKey:@"c_user_session"])
|
||||
return [r objectForKey:@"c_user_session"];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
- (NSException *) writeOpenIdSession: (NSString *) _user_session
|
||||
withOldSession: (NSString *) _old_session
|
||||
withRefreshToken: (NSString *) _refresh_token
|
||||
withExpire: (NSNumber *) _expire
|
||||
withRefreshExpire: (NSNumber *) _refresh_expire
|
||||
{
|
||||
NSDictionary *record, *newRecord;
|
||||
NSException *error;
|
||||
NSCalendarDate *nowDate;
|
||||
int now, nowExpire, nowRefreshExpire;
|
||||
EOAdaptorChannel *tc;
|
||||
EOAdaptorContext *context;
|
||||
EOEntity *entity;
|
||||
EOSQLQualifier *qualifier;
|
||||
|
||||
error = nil;
|
||||
tc = [self _acquireStoreChannel];
|
||||
if (tc)
|
||||
{
|
||||
context = [tc adaptorContext];
|
||||
|
||||
nowDate = [NSCalendarDate date];
|
||||
now = (nowDate ? (int)[nowDate timeIntervalSince1970] : 0);
|
||||
nowExpire = now + [_expire intValue];
|
||||
if(_refresh_expire)
|
||||
nowRefreshExpire = now + [_refresh_expire intValue];
|
||||
else
|
||||
nowRefreshExpire = -1;
|
||||
if(!_old_session)
|
||||
_old_session = @"";
|
||||
|
||||
newRecord = [NSDictionary dictionaryWithObjectsAndKeys: _user_session, @"c_user_session",
|
||||
_old_session, @"c_old_session",
|
||||
[NSNumber numberWithInt:now], @"c_session_started",
|
||||
_refresh_token, @"c_refresh_token",
|
||||
[NSNumber numberWithInt:nowExpire] , @"c_access_token_expires_in",
|
||||
[NSNumber numberWithInt:nowRefreshExpire] , @"c_refresh_token_expires_in",
|
||||
nil];
|
||||
record = [self recordForSession: _user_session useOldSession: NO];
|
||||
entity = [self _storeTableEntityForChannel: tc];
|
||||
[context beginTransaction];
|
||||
if (!record)
|
||||
{
|
||||
//If the session already exist no need to update it as it is unique
|
||||
error = [tc insertRowX: newRecord forEntity: entity];
|
||||
}
|
||||
|
||||
if (error)
|
||||
{
|
||||
[context rollbackTransaction];
|
||||
[self errorWithFormat:@"%s: cannot write record: %@", __PRETTY_FUNCTION__, error];
|
||||
}
|
||||
else
|
||||
[context commitTransaction];
|
||||
[self _releaseChannel: tc];
|
||||
}
|
||||
return error;
|
||||
}
|
||||
|
||||
- (NSException *) deleteOpenIdSessionFor: (NSString *) _user_session
|
||||
{
|
||||
EOAdaptorChannel *tc;
|
||||
EOAdaptorContext *context;
|
||||
EOEntity *entity;
|
||||
EOSQLQualifier *qualifier;
|
||||
NSException *error;
|
||||
|
||||
error = nil;
|
||||
tc = [self _acquireStoreChannel];
|
||||
if (tc)
|
||||
{
|
||||
context = [tc adaptorContext];
|
||||
entity = [self _storeTableEntityForChannel: tc];
|
||||
qualifier = [[EOSQLQualifier alloc] initWithEntity: entity
|
||||
qualifierFormat: @"c_user_session='%@'",_user_session];
|
||||
[qualifier autorelease];
|
||||
[context beginTransaction];
|
||||
error = [tc deleteRowsDescribedByQualifierX: qualifier];
|
||||
if (error)
|
||||
{
|
||||
[context rollbackTransaction];
|
||||
[self errorWithFormat:@"%s: cannot delete record: %@", __PRETTY_FUNCTION__, error];
|
||||
}
|
||||
else
|
||||
[context commitTransaction];
|
||||
[self _releaseChannel: tc];
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -42,6 +42,9 @@
|
||||
- (NSString *)createAdminFolderWithName:(NSString *)tableName;
|
||||
- (NSDictionary *)adminAttributeTypes;
|
||||
|
||||
- (NSString *)createOpenIdFolderWithName:(NSString *)tableName;
|
||||
- (NSDictionary *)openIdAttributeTypes;
|
||||
|
||||
- (NSString *) updateCPathInFolderInfo:(NSString *)tableName
|
||||
withCPath2:(NSString *)c_path2;
|
||||
|
||||
|
||||
@@ -95,6 +95,21 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *) createOpenIdFolderWithName: (NSString *) tableName
|
||||
{
|
||||
[self subclassResponsibility: _cmd];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSDictionary *) openIdAttributeTypes
|
||||
{
|
||||
[self subclassResponsibility: _cmd];
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
|
||||
- (NSString *) createFolderTableWithName: (NSString *) tableName
|
||||
{
|
||||
[self subclassResponsibility: _cmd];
|
||||
@@ -195,6 +210,38 @@
|
||||
return types;
|
||||
}
|
||||
|
||||
- (NSString *) createOpenIdFolderWithName: (NSString *) tableName
|
||||
{
|
||||
static NSString *sqlFolderFormat
|
||||
= (@"CREATE TABLE %@ ("
|
||||
@" c_user_session VARCHAR(4096) NOT NULL,"
|
||||
@" c_old_session VARCHAR(4096) NULL,"
|
||||
@" c_session_started INT4 NOT NULL,"
|
||||
@" c_refresh_token VARCHAR(4096) NULL,"
|
||||
@" c_access_token_expires_in INT4 NOT NULL,"
|
||||
@" c_refresh_token_expires_in INT4 NULL)");
|
||||
|
||||
return [NSString stringWithFormat: sqlFolderFormat, tableName];
|
||||
}
|
||||
|
||||
- (NSDictionary *) openIdAttributeTypes
|
||||
{
|
||||
static NSMutableDictionary *types = nil;
|
||||
|
||||
if (!types)
|
||||
{
|
||||
types = [NSMutableDictionary new];
|
||||
[types setObject: @"varchar" forKey: @"c_user_session"];
|
||||
[types setObject: @"varchar" forKey: @"c_old_session"];
|
||||
[types setObject: @"int" forKey: @"c_session_started"];
|
||||
[types setObject: @"varchar" forKey: @"c_refresh_token"];
|
||||
[types setObject: @"int" forKey: @"c_access_token_expires_in"];
|
||||
[types setObject: @"int" forKey: @"c_refresh_token_expires_in"];
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
- (NSString *) createFolderTableWithName: (NSString *) tableName
|
||||
{
|
||||
static NSString *sqlFolderFormat
|
||||
@@ -324,6 +371,38 @@
|
||||
return types;
|
||||
}
|
||||
|
||||
- (NSString *) createOpenIdFolderWithName: (NSString *) tableName
|
||||
{
|
||||
static NSString *sqlFolderFormat
|
||||
= (@"CREATE TABLE %@ ("
|
||||
@" c_user_session VARCHAR(4096) NOT NULL,"
|
||||
@" c_old_session VARCHAR(4096) NULL,"
|
||||
@" c_session_started INT4 NOT NULL,"
|
||||
@" c_refresh_token VARCHAR(4096) NULL,"
|
||||
@" c_access_token_expires_in INT4 NOT NULL,"
|
||||
@" c_refresh_token_expires_in INT4 NULL)");
|
||||
|
||||
return [NSString stringWithFormat: sqlFolderFormat, tableName];
|
||||
}
|
||||
|
||||
- (NSDictionary *) openIdAttributeTypes
|
||||
{
|
||||
static NSMutableDictionary *types = nil;
|
||||
|
||||
if (!types)
|
||||
{
|
||||
types = [NSMutableDictionary new];
|
||||
[types setObject: @"varchar" forKey: @"c_user_session"];
|
||||
[types setObject: @"varchar" forKey: @"c_old_session"];
|
||||
[types setObject: @"int" forKey: @"c_session_started"];
|
||||
[types setObject: @"varchar" forKey: @"c_refresh_token"];
|
||||
[types setObject: @"int" forKey: @"c_access_token_expires_in"];
|
||||
[types setObject: @"int" forKey: @"c_refresh_token_expires_in"];
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
- (NSString *) createFolderTableWithName: (NSString *) tableName
|
||||
{
|
||||
static NSString *sqlFolderFormat
|
||||
@@ -453,6 +532,38 @@
|
||||
return types;
|
||||
}
|
||||
|
||||
- (NSString *) createOpenIdFolderWithName: (NSString *) tableName
|
||||
{
|
||||
static NSString *sqlFolderFormat
|
||||
= (@"CREATE TABLE %@ ("
|
||||
@" c_user_session VARCHAR2(4096) NOT NULL,"
|
||||
@" c_old_session VARCHAR2(4096) NULL,"
|
||||
@" c_session_started INTEGER NOT NULL,"
|
||||
@" c_refresh_token VARCHAR2(4096) NULL,"
|
||||
@" c_access_token_expires_in INTEGER NOT NULL,"
|
||||
@" c_refresh_token_expires_in INTEGER NULL)");
|
||||
|
||||
return [NSString stringWithFormat: sqlFolderFormat, tableName];
|
||||
}
|
||||
|
||||
- (NSDictionary *) openIdAttributeTypes
|
||||
{
|
||||
static NSMutableDictionary *types = nil;
|
||||
|
||||
if (!types)
|
||||
{
|
||||
types = [NSMutableDictionary new];
|
||||
[types setObject: @"varchar2" forKey: @"c_user_session"];
|
||||
[types setObject: @"varchar2" forKey: @"c_old_session"];
|
||||
[types setObject: @"integer" forKey: @"c_session_started"];
|
||||
[types setObject: @"varchar2" forKey: @"c_refresh_token"];
|
||||
[types setObject: @"integer" forKey: @"c_access_token_expires_in"];
|
||||
[types setObject: @"integer" forKey: @"c_refresh_token_expires_in"];
|
||||
}
|
||||
|
||||
return types;
|
||||
}
|
||||
|
||||
- (NSString *) createFolderTableWithName: (NSString *) tableName
|
||||
{
|
||||
static NSString *sqlFolderFormat
|
||||
|
||||
@@ -28,13 +28,14 @@ libGDLContentStore_HEADER_FILES += \
|
||||
GCSAdminFolder.h \
|
||||
GCSContext.h \
|
||||
GCSFieldInfo.h \
|
||||
GCSFolder.h \
|
||||
GCSFolder.h \
|
||||
GCSFolderManager.h \
|
||||
GCSFolderType.h \
|
||||
GCSChannelManager.h \
|
||||
GCSOpenIdFolder.h \
|
||||
GCSSessionsFolder.h \
|
||||
GCSSpecialQueries.h \
|
||||
GCSStringFormatter.h \
|
||||
GCSStringFormatter.h \
|
||||
|
||||
libGDLContentStore_OBJC_FILES += \
|
||||
NSURL+GCS.m \
|
||||
@@ -45,13 +46,14 @@ libGDLContentStore_OBJC_FILES += \
|
||||
GCSAdminFolder.m \
|
||||
GCSContext.m \
|
||||
GCSFieldInfo.m \
|
||||
GCSFolder.m \
|
||||
GCSFolder.m \
|
||||
GCSFolderManager.m \
|
||||
GCSFolderType.m \
|
||||
GCSChannelManager.m \
|
||||
GCSOpenIdFolder.m \
|
||||
GCSSessionsFolder.m \
|
||||
GCSSpecialQueries.m \
|
||||
GCSStringFormatter.m \
|
||||
GCSStringFormatter.m \
|
||||
|
||||
# framework support
|
||||
|
||||
|
||||
@@ -464,7 +464,8 @@ lastPossibleRecurrenceStartDateUsingFirstInstanceCalendarDateRange: (NGCalendarD
|
||||
}
|
||||
}
|
||||
|
||||
if ([self hasRecurrenceDates])
|
||||
//If there are recurrence rules and we have no date yet, its means it's infinite and rdates shouldn't be count as last dates.
|
||||
if ([self hasRecurrenceDates] && !([self hasRecurrenceRules] && !date))
|
||||
{
|
||||
NSEnumerator *rDates;
|
||||
|
||||
|
||||
@@ -165,6 +165,16 @@ CREATE TABLE sogo_admin (
|
||||
PRIMARY KEY (c_key)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
|
||||
|
||||
CREATE TABLE sogo_opend_id (
|
||||
c_user_session varchar(4096) NOT NULL,
|
||||
c_old_session varchar(4096) DEFAULT '',
|
||||
c_session_started int(11) NOT NULL,
|
||||
c_refresh_token varchar(4096) DEFAULT '',
|
||||
c_access_token_expires_in int(11) NOT NULL,
|
||||
c_refresh_token_expires_in int(11) DEFAULT NULL,
|
||||
PRIMARY KEY (c_user_session)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
|
||||
|
||||
CREATE TABLE sogo_user_profile (
|
||||
c_uid varchar(255) NOT NULL,
|
||||
c_defaults longtext,
|
||||
|
||||
@@ -1130,28 +1130,26 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
delta = [masterEndDate timeIntervalSinceDate: [master startDate]];
|
||||
recurrenceIdRange = [NGCalendarDateRange calendarDateRangeWithStartDate: recurrenceId
|
||||
endDate: [recurrenceId dateByAddingYears:0 months:0 days:0 hours:0 minutes:0 seconds: delta]];
|
||||
if ([dateRange doesIntersectWithDateRange: recurrenceIdRange])
|
||||
recordIndex = [self _indexOfRecordMatchingDate: recurrenceId inArray: ma];
|
||||
if ([dateRange doesIntersectWithDateRange: recurrenceIdRange] && recordIndex == -1) {
|
||||
[self warnWithFormat:
|
||||
@"missing exception record for recurrence-id %@ (uid %@), ma = %@, dr = %@, rir = %@",
|
||||
recurrenceId, [component uid], ma, dateRange, recurrenceIdRange];
|
||||
}
|
||||
if ([dateRange doesIntersectWithDateRange: recurrenceIdRange] && recordIndex != -1)
|
||||
{
|
||||
// The recurrence exception intersects with the date range;
|
||||
// find the occurence and replace it with the new record
|
||||
recordIndex = [self _indexOfRecordMatchingDate: recurrenceId inArray: ma];
|
||||
if (recordIndex > -1)
|
||||
{
|
||||
if ([dateRange containsDate: [component startDate]] ||
|
||||
(endDate && [dateRange containsDate: endDate]))
|
||||
{
|
||||
// We must pass nil to :container here in order to avoid re-entrancy issues.
|
||||
newRecord = [self _fixupRecord: [component quickRecordFromContent: nil container: nil nameInContainer: nil]];
|
||||
[ma replaceObjectAtIndex: recordIndex withObject: newRecord];
|
||||
}
|
||||
else
|
||||
// The range doesn't cover the exception; remove it from the records
|
||||
[ma removeObjectAtIndex: recordIndex];
|
||||
}
|
||||
else
|
||||
[self errorWithFormat:
|
||||
@"missing exception record for recurrence-id %@ (uid %@)",
|
||||
recurrenceId, [component uid]];
|
||||
if ([dateRange containsDate: [component startDate]] ||
|
||||
(endDate && [dateRange containsDate: endDate]))
|
||||
{
|
||||
// We must pass nil to :container here in order to avoid re-entrancy issues.
|
||||
newRecord = [self _fixupRecord: [component quickRecordFromContent: nil container: nil nameInContainer: nil]];
|
||||
[ma replaceObjectAtIndex: recordIndex withObject: newRecord];
|
||||
}
|
||||
else
|
||||
// The range doesn't cover the exception; remove it from the records
|
||||
[ma removeObjectAtIndex: recordIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1762,7 +1760,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
|
||||
{
|
||||
NSCalendarDate *now, *rc;
|
||||
|
||||
if (davCalendarStartTimeLimit > 0 && ![[context activeUser] isSuperUser])
|
||||
if ([[context request] isSoWebDAVRequest] && davCalendarStartTimeLimit > 0 && ![[context activeUser] isSuperUser])
|
||||
{
|
||||
now = [NSCalendarDate date];
|
||||
rc = [now addTimeInterval: -davTimeLimitSeconds];
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#import <Foundation/NSString.h>
|
||||
|
||||
#import <NGCards/iCalEvent.h>
|
||||
#import <NGCards/iCalDateTime.h>
|
||||
#import <NGCards/iCalRepeatableEntityObject.h>
|
||||
|
||||
#import "iCalCalendar+SOGo.h"
|
||||
@@ -97,7 +98,29 @@
|
||||
elements = [self allObjects];
|
||||
count = [elements count];
|
||||
if (count)
|
||||
{
|
||||
element = [elements objectAtIndex: 0];
|
||||
if(count >1)
|
||||
{
|
||||
//If there more than one event, it may because there is the main reccurent event and some exceptions with reccurenceId
|
||||
//It's important to fetch the "main" vevent and not the other.
|
||||
NSEnumerator *elementEnum;
|
||||
NSCalendarDate *recurrenceId;
|
||||
CardGroup *elem;
|
||||
elementEnum = [elements objectEnumerator];
|
||||
while((elem = [elementEnum nextObject]))
|
||||
{
|
||||
recurrenceId = [(iCalDateTime *) [elem uniqueChildWithTag: @"recurrence-id"] dateTime];
|
||||
if(recurrenceId)
|
||||
continue;
|
||||
else
|
||||
{
|
||||
element = elem;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
//NSLog(@"ERROR: given calendar contains no elements: %@", self);
|
||||
|
||||
@@ -379,7 +379,7 @@ NSNumber *iCalDistantFutureNumber = nil;
|
||||
currentAttendee = [attendees nextObject];
|
||||
while (!isAttendee
|
||||
&& currentAttendee)
|
||||
if ([user hasEmail: [currentAttendee rfc822Email]])
|
||||
if ([user hasEmail: [currentAttendee rfc822Email]] && ![self isOrganizer:[currentAttendee rfc822Email]]) // Exclude organizer if in attendees
|
||||
isAttendee = YES;
|
||||
else
|
||||
currentAttendee = [attendees nextObject];
|
||||
|
||||
@@ -291,6 +291,7 @@ static NSString *userAgent = nil;
|
||||
{
|
||||
[headers setObject: @"true" forKey: @"receipt"];
|
||||
[headers setObject: receipt forKey: @"Disposition-Notification-To"];
|
||||
[headers setObject: receipt forKey: @"Return-Receipt-To"];
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -302,12 +303,14 @@ static NSString *userAgent = nil;
|
||||
if (pureSender)
|
||||
{
|
||||
[headers setObject: pureSender forKey: @"Disposition-Notification-To"];
|
||||
[headers setObject: pureSender forKey: @"Return-Receipt-To"];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[headers removeObjectForKey: @"receipt"];
|
||||
[headers removeObjectForKey: @"Disposition-Notification-To"];
|
||||
[headers removeObjectForKey: @"Return-Receipt-To"];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -990,7 +993,6 @@ static NSString *userAgent = nil;
|
||||
[self _fileAttachmentsFromPart: [m body] onlyImages: onlyImages];
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
@@ -1019,7 +1021,7 @@ static NSString *userAgent = nil;
|
||||
|
||||
addresses = [NSMutableArray array];
|
||||
[self _addEMailsOfAddresses: [sourceEnvelope from] toArray: addresses];
|
||||
if ([addresses count])
|
||||
if ([addresses count] > 0)
|
||||
[info setObject: [addresses objectAtIndex: 0] forKey: @"from"];
|
||||
addresses = [NSMutableArray array];
|
||||
[self _addEMailsOfAddresses: [sourceEnvelope to] toArray: addresses];
|
||||
@@ -1035,15 +1037,17 @@ static NSString *userAgent = nil;
|
||||
addresses = [NSMutableArray array];
|
||||
[self _addEMailsOfAddresses: [sourceEnvelope replyTo] toArray: addresses];
|
||||
if ([addresses count] > 0)
|
||||
[info setObject: addresses forKey: @"replyTo"];
|
||||
[info setObject: [addresses objectAtIndex: 0] forKey: @"replyTo"];
|
||||
|
||||
h = [sourceMail mailHeaders];
|
||||
priority = [h objectForKey: @"x-priority"];
|
||||
if ([priority isNotEmpty] && [priority isKindOfClass: [NSString class]])
|
||||
[info setObject: (NSString*)priority forKey: @"X-Priority"];
|
||||
receipt = [h objectForKey: @"disposition-notification-to"];
|
||||
if ([receipt isNotEmpty] && [receipt isKindOfClass: [NSString class]])
|
||||
if ([receipt isNotEmpty] && [receipt isKindOfClass: [NSString class]]) {
|
||||
[info setObject: (NSString*)receipt forKey: @"Disposition-Notification-To"];
|
||||
[info setObject: (NSString*)receipt forKey: @"Return-Receipt-To"];
|
||||
}
|
||||
|
||||
ud = [[context activeUser] userDefaults];
|
||||
|
||||
@@ -1893,6 +1897,8 @@ static NSString *userAgent = nil;
|
||||
[map setObject: s forKey: @"X-Priority"];
|
||||
if ([(s = [headers objectForKey: @"Disposition-Notification-To"]) length] > 0)
|
||||
[map setObject: s forKey: @"Disposition-Notification-To"];
|
||||
if ([(s = [headers objectForKey: @"Return-Receipt-To"]) length] > 0)
|
||||
[map setObject: s forKey: @"Return-Receipt-To"];
|
||||
|
||||
[self _addHeaders: _headers toHeaderMap: map];
|
||||
|
||||
|
||||
@@ -104,6 +104,7 @@ typedef enum {
|
||||
- (NSArray *) toManyRelationshipKeysWithNamespaces: (BOOL) withNSs;
|
||||
|
||||
- (NSArray *) allFolderPaths: (SOGoMailListingMode) theListingMode;
|
||||
- (NSArray *) allFolderPaths: (SOGoMailListingMode) theListingMode onlyRoot: (BOOL) onlyRoot;
|
||||
- (NSArray *) allFoldersMetadata: (SOGoMailListingMode) theListingMode;
|
||||
|
||||
- (NSDictionary *) imapFolderGUIDs;
|
||||
|
||||
@@ -378,14 +378,21 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
return folders;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (NSArray *) allFolderPaths: (SOGoMailListingMode) theListingMode
|
||||
- (NSArray *) allFolderPaths: (SOGoMailListingMode) theListingMode
|
||||
{
|
||||
NSMutableArray *folderPaths, *namespaces;
|
||||
return [self allFolderPaths: theListingMode onlyRoot: NO];
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (NSArray *) allFolderPaths: (SOGoMailListingMode) theListingMode onlyRoot: (BOOL) onlyRoot
|
||||
{
|
||||
NSMutableArray *folderPaths, *namespaces, *filteredFolders;
|
||||
NSArray *folders, *mainFolders;
|
||||
NSString *namespace;
|
||||
NSString *folder;
|
||||
NSUInteger slashCount;
|
||||
|
||||
BOOL subscribedOnly;
|
||||
int count, max;
|
||||
@@ -401,10 +408,10 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
onlySubscribedFolders: YES];
|
||||
max = [folders count];
|
||||
for (count = 0; count < max; count++)
|
||||
{
|
||||
[subscribedFolders setObject: [NSNull null]
|
||||
forKey: [folders objectAtIndex: count]];
|
||||
}
|
||||
{
|
||||
[subscribedFolders setObject: [NSNull null]
|
||||
forKey: [folders objectAtIndex: count]];
|
||||
}
|
||||
[[self imap4Connection] flushFolderHierarchyCache];
|
||||
}
|
||||
|
||||
@@ -418,6 +425,17 @@ static NSString *inboxFolderName = @"INBOX";
|
||||
nil] stringsWithFormat: @"/%@"];
|
||||
folders = [[self imap4Connection] allFoldersForURL: [self imap4URL]
|
||||
onlySubscribedFolders: subscribedOnly];
|
||||
if (onlyRoot) {
|
||||
filteredFolders = [[NSMutableArray alloc] init];
|
||||
for (folder in folders) {
|
||||
slashCount = [[folder componentsSeparatedByString:@"/"] count] - 1;
|
||||
if (slashCount <= 1) {
|
||||
[filteredFolders addObject: folder];
|
||||
}
|
||||
}
|
||||
folders = [NSArray arrayWithArray: filteredFolders];
|
||||
[filteredFolders release];
|
||||
}
|
||||
folderPaths = [folders mutableCopy];
|
||||
[folderPaths autorelease];
|
||||
|
||||
|
||||
@@ -132,14 +132,24 @@
|
||||
|
||||
- (NGImap4ConnectionManager *) mailManager
|
||||
{
|
||||
return [NGImap4ConnectionManager defaultConnectionManager];
|
||||
SOGoSystemDefaults *sd;
|
||||
NSString *imapAuthMech, *domain;
|
||||
|
||||
domain = [[[self context] activeUser] loginDomain];
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
if([sd doesLoginTypeByDomain])
|
||||
imapAuthMech = [sd getImapAuthMechForDomain: domain];
|
||||
else
|
||||
imapAuthMech = nil;
|
||||
|
||||
return [NGImap4ConnectionManager defaultConnectionManager: imapAuthMech];
|
||||
}
|
||||
|
||||
- (NGImap4Connection *) _createIMAP4Connection
|
||||
{
|
||||
NGImap4ConnectionManager *manager;
|
||||
NGImap4Connection *newConnection;
|
||||
NSString *password;
|
||||
NSString *password, *domain;
|
||||
NGInternetSocketAddress *host;
|
||||
SOGoSystemDefaults *sd;
|
||||
BOOL usesSSO;
|
||||
@@ -152,12 +162,15 @@
|
||||
host = [NGInternetSocketAddress addressWithPort:0 onHost:[[self imap4URL] host]];
|
||||
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
usesSSO = [[sd authenticationType] isEqualToString: @"cas"] || [[sd authenticationType] isEqualToString: @"saml2"];
|
||||
|
||||
domain = [[[self context] activeUser] loginDomain];
|
||||
usesSSO = [sd isSsoUsed: domain];
|
||||
|
||||
if (![[[self mailAccountFolder] nameInContainer] isEqualToString: @"0"] &&
|
||||
usesSSO &&
|
||||
[host isLocalhost])
|
||||
{
|
||||
//
|
||||
[self errorWithFormat: @"Trying to use localhost for additional IMAP account - aborting."];
|
||||
return nil;
|
||||
}
|
||||
@@ -227,19 +240,17 @@
|
||||
login = [[[self context] activeUser] login];
|
||||
|
||||
if (!login)
|
||||
login = [[[[self container] context] activeUser] login];
|
||||
login = [[[[self container] context] activeUser] login];
|
||||
|
||||
cacheKey = [NSString stringWithFormat: @"%@+%@",
|
||||
login,
|
||||
[[self mailAccountFolder] nameInContainer]];
|
||||
cacheKey = [NSString stringWithFormat: @"%@+%@", login, [[self mailAccountFolder] nameInContainer]];
|
||||
imap4 = [sogoCache imap4ConnectionForKey: cacheKey];
|
||||
if (!imap4)
|
||||
{
|
||||
imap4 = [self _createIMAP4Connection];
|
||||
[sogoCache registerIMAP4Connection: imap4
|
||||
forKey: cacheKey];
|
||||
}
|
||||
[imap4 retain];
|
||||
{
|
||||
imap4 = [self _createIMAP4Connection];
|
||||
[sogoCache registerIMAP4Connection: imap4
|
||||
forKey: cacheKey];
|
||||
}
|
||||
[imap4 retain];
|
||||
}
|
||||
|
||||
// Connection broken, try to reconnect
|
||||
|
||||
@@ -953,7 +953,10 @@ static BOOL debugSoParts = NO;
|
||||
body = [NSData data];
|
||||
|
||||
currentAttachment = [NSMutableDictionary dictionaryWithDictionary: currentInfo];
|
||||
[currentAttachment setObject: body forKey: @"body"];
|
||||
if (body)
|
||||
[currentAttachment setObject: body forKey: @"body"];
|
||||
else
|
||||
[self errorWithFormat: @"Failed add attachement %@, empty body - failed to decode : %@", currentPath, currentInfo];
|
||||
[attachments addObject: currentAttachment];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,6 +69,7 @@ SOGo_HEADER_FILES = \
|
||||
SOGoAuthenticator.h \
|
||||
SOGoSession.h \
|
||||
SOGoCASSession.h \
|
||||
SOGoOpenIdSession.h \
|
||||
SOGoDAVAuthenticator.h \
|
||||
SOGoProxyAuthenticator.h \
|
||||
SOGoStaticAuthenticator.h \
|
||||
@@ -161,6 +162,7 @@ SOGo_OBJC_FILES = \
|
||||
\
|
||||
SOGoSession.m \
|
||||
SOGoCASSession.m \
|
||||
SOGoOpenIdSession.m \
|
||||
SOGoDAVAuthenticator.m \
|
||||
SOGoProxyAuthenticator.m \
|
||||
SOGoStaticAuthenticator.m \
|
||||
|
||||
@@ -17,6 +17,7 @@ SOGo_LIBRARIES_DEPEND_UPON += \
|
||||
-Wl,--no-as-needed \
|
||||
-L../../SOPE/NGCards/$(GNUSTEP_OBJ_DIR)/ \
|
||||
-lmemcached \
|
||||
-lcurl \
|
||||
-lGDLAccess \
|
||||
-lNGObjWeb \
|
||||
-lNGCards \
|
||||
|
||||
@@ -1429,11 +1429,22 @@ groupObjectClasses: (NSArray *) newGroupObjectClasses
|
||||
NSMutableArray *contacts;
|
||||
EOQualifier *qualifier;
|
||||
unsigned int i;
|
||||
NSMutableString *s;
|
||||
NSString *sortAttribute;
|
||||
BOOL sortReverse;
|
||||
|
||||
contacts = [NSMutableArray array];
|
||||
|
||||
if(theDomain != nil && [theDomain length] > 0)
|
||||
{
|
||||
if ([_pristineBaseDN rangeOfString: @"%d"].location != NSNotFound)
|
||||
{
|
||||
s = [NSMutableString stringWithString: _pristineBaseDN];
|
||||
[s replaceOccurrencesOfString: @"%d" withString: theDomain options: 0 range: NSMakeRange(0, [s length])];
|
||||
ASSIGN(_baseDN, s);
|
||||
}
|
||||
}
|
||||
|
||||
if ([match length] > 0 || !_listRequiresDot)
|
||||
{
|
||||
ldapConnection = [self _ldapConnection];
|
||||
@@ -2290,8 +2301,7 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection,
|
||||
NSRange r;
|
||||
|
||||
r = [theLogin rangeOfString: @"@"];
|
||||
if (r.location != NSNotFound &&
|
||||
[_pristineBaseDN rangeOfString: @"%d"].location != NSNotFound)
|
||||
if (r.location != NSNotFound && [_pristineBaseDN rangeOfString: @"%d"].location != NSNotFound)
|
||||
{
|
||||
s = [NSMutableString stringWithString: _pristineBaseDN];
|
||||
[s replaceOccurrencesOfString: @"%d" withString: [theLogin substringFromIndex: r.location+1] options: 0 range: NSMakeRange(0, [s length])];
|
||||
@@ -2306,7 +2316,7 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection,
|
||||
|
||||
- (NSArray *) membersForGroupWithUID: (NSString *) uid
|
||||
{
|
||||
NSMutableArray *dns, *uids;
|
||||
NSMutableArray *dns, *uids, *userLogins;
|
||||
NSString *dn, *login;
|
||||
SOGoUserManager *um;
|
||||
NSDictionary *d, *contactInfos;
|
||||
@@ -2327,6 +2337,7 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection,
|
||||
members = [NSMutableArray new];
|
||||
uids = [NSMutableArray array];
|
||||
dns = [NSMutableArray array];
|
||||
userLogins = [NSMutableArray array];
|
||||
|
||||
// We check if it's a static group
|
||||
// Fetch "members" - we get DNs
|
||||
@@ -2358,6 +2369,13 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection,
|
||||
pool = [NSAutoreleasePool new];
|
||||
dn = [dns objectAtIndex: i];
|
||||
login = [um getLoginForDN: [dn lowercaseString]];
|
||||
if([userLogins containsObject: login])
|
||||
{
|
||||
[pool release];
|
||||
continue; //user alrady fetch
|
||||
}
|
||||
if(login != nil)
|
||||
[userLogins addObject: login];
|
||||
user = [SOGoUser userWithLogin: login roles: nil];
|
||||
if (user)
|
||||
{
|
||||
@@ -2385,6 +2403,13 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection,
|
||||
{
|
||||
pool = [NSAutoreleasePool new];
|
||||
login = [uids objectAtIndex: i];
|
||||
if([userLogins containsObject: login])
|
||||
{
|
||||
[pool release];
|
||||
continue; //user alrady fetch
|
||||
}
|
||||
if(login != nil)
|
||||
[userLogins addObject: login];
|
||||
user = [SOGoUser userWithLogin: login roles: nil];
|
||||
if (user)
|
||||
{
|
||||
@@ -2456,4 +2481,4 @@ _makeLDAPChanges (NGLdapConnection *ldapConnection,
|
||||
return rc;
|
||||
}
|
||||
|
||||
@end
|
||||
@end
|
||||
@@ -95,6 +95,7 @@
|
||||
return keysWithFormat;
|
||||
}
|
||||
|
||||
|
||||
- (NSComparisonResult) caseInsensitiveDisplayNameCompare: (NSDictionary *) theDictionary
|
||||
{
|
||||
return [[self objectForKey: @"cn"] caseInsensitiveCompare: [theDictionary objectForKey: @"cn"]];
|
||||
|
||||
@@ -80,7 +80,10 @@ static int cssEscapingCount;
|
||||
{
|
||||
hostR = [self rangeOfString: @"://"];
|
||||
locationR = [[self substringFromIndex: (hostR.location + hostR.length)] rangeOfString: @"/"];
|
||||
newURL = [self substringFromIndex: (hostR.location + hostR.length + locationR.location)];
|
||||
if(locationR.location != NSNotFound)
|
||||
newURL = [self substringFromIndex: (hostR.location + hostR.length + locationR.location)];
|
||||
else
|
||||
newURL = @"";
|
||||
}
|
||||
|
||||
return newURL;
|
||||
|
||||
@@ -119,6 +119,13 @@
|
||||
|
||||
- (void) removeCASSessionWithTicket: (NSString *) ticket;
|
||||
|
||||
// OpenId Support
|
||||
- (NSString *) openIdSessionFromServer: (NSString *) endpoint;
|
||||
- (void) setOpenIdSession: (NSString *) openIdSession
|
||||
forServer: (NSString *) endpoint;
|
||||
- (NSString *) userOpendIdSessionFromServer: (NSString *) endpoint;
|
||||
- (void) setUserOpendIdSessionFromServer: (NSString *) endpoint
|
||||
nextCheckAfter: (NSString *) nextCheck;
|
||||
//
|
||||
// SAML2 support
|
||||
//
|
||||
|
||||
@@ -683,6 +683,32 @@ static memcached_st *handle = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// OpenId support
|
||||
|
||||
- (NSString *) openIdSessionFromServer: (NSString *) endpoint
|
||||
{
|
||||
return [self valueForKey: [NSString stringWithFormat: @"openId:%@", endpoint]];
|
||||
}
|
||||
|
||||
- (void) setOpenIdSession: (NSString *) openIdSession
|
||||
forServer: (NSString *) endpoint
|
||||
{
|
||||
[self setValue: openIdSession
|
||||
forKey: [NSString stringWithFormat: @"openId:%@", endpoint]];
|
||||
}
|
||||
|
||||
- (NSString *) userOpendIdSessionFromServer: (NSString *) identifier
|
||||
{
|
||||
return [self valueForKey: [NSString stringWithFormat: @"userOpenId:%@", identifier]];
|
||||
}
|
||||
- (void) setUserOpendIdSessionFromServer: (NSString *) identifier
|
||||
nextCheckAfter: (NSString *) nextCheck
|
||||
{
|
||||
[self setValue: nextCheck
|
||||
forKey: [NSString stringWithFormat: @"userOpenId:%@", identifier]];
|
||||
}
|
||||
|
||||
//
|
||||
// SAML2 support
|
||||
//
|
||||
|
||||
@@ -65,7 +65,8 @@
|
||||
domain: &domain
|
||||
perr: &perr
|
||||
expire: &expire
|
||||
grace: &grace]
|
||||
grace: &grace
|
||||
additionalInfo: nil]
|
||||
&& perr == PolicyNoError);
|
||||
|
||||
if (!rc)
|
||||
@@ -140,6 +141,7 @@
|
||||
return password;
|
||||
}
|
||||
|
||||
|
||||
/* create SOGoUser */
|
||||
|
||||
- (SOGoUser *) userInContext: (WOContext *)_ctx
|
||||
|
||||
@@ -62,6 +62,7 @@ extern NSString *SOGoDefaultsSourceUnmutableSource;
|
||||
|
||||
- (void) setBool: (BOOL) value forKey: (NSString *) key;
|
||||
- (BOOL) boolForKey: (NSString *) key;
|
||||
- (BOOL) boolForKey: (NSString *) key andDict: (NSDictionary*) _dict;
|
||||
|
||||
- (void) setFloat: (float) value forKey: (NSString *) key;
|
||||
- (float) floatForKey: (NSString *) key;
|
||||
|
||||
@@ -164,6 +164,29 @@ static Class NSStringKlass = Nil;
|
||||
return value;
|
||||
}
|
||||
|
||||
- (BOOL) boolForKey: (NSString *) key andDict: (NSDictionary*) _dict
|
||||
{
|
||||
id boolForKey;
|
||||
BOOL value;
|
||||
|
||||
boolForKey = [_dict objectForKey: key];
|
||||
if (boolForKey)
|
||||
{
|
||||
if ([boolForKey respondsToSelector: @selector (boolValue)])
|
||||
value = [boolForKey boolValue];
|
||||
else
|
||||
{
|
||||
[self warnWithFormat: @"expected a boolean for '%@' (ignored)",
|
||||
key];
|
||||
value = NO;
|
||||
}
|
||||
}
|
||||
else
|
||||
value = NO;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
- (void) setFloat: (float) value
|
||||
forKey: (NSString *) key
|
||||
{
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
- (NSString *) vacationDefaultSubject;
|
||||
- (NSString *) vacationHeaderTemplateFile;
|
||||
- (NSString *) vacationFooterTemplateFile;
|
||||
- (BOOL) vacationAllowZeroDays;
|
||||
- (NSString *) mailingMechanism;
|
||||
- (NSString *) smtpServer;
|
||||
- (BOOL)smtpMasterUserEnabled;
|
||||
@@ -77,6 +78,7 @@
|
||||
- (BOOL) appointmentSendEMailNotifications;
|
||||
- (BOOL) foldersSendEMailNotifications;
|
||||
- (NSArray *) calendarDefaultRoles;
|
||||
- (NSString *) calendarJistiBaseUrl;
|
||||
- (NSArray *) contactsDefaultRoles;
|
||||
- (NSArray *) refreshViewIntervals;
|
||||
- (NSString *) subscriptionFolderFormat;
|
||||
|
||||
@@ -169,6 +169,16 @@
|
||||
return [self stringArrayForKey: @"SOGoCalendarDefaultRoles"];
|
||||
}
|
||||
|
||||
- (NSString *) calendarJistiBaseUrl
|
||||
{
|
||||
NSString *jitsiBaseUrl;
|
||||
jitsiBaseUrl = [self stringForKey: @"SOGoCalendarJitsiBaseUrl"];
|
||||
if(!jitsiBaseUrl)
|
||||
jitsiBaseUrl = @"https://meet.jit.si";
|
||||
|
||||
return jitsiBaseUrl;
|
||||
}
|
||||
|
||||
- (NSArray *) contactsDefaultRoles
|
||||
{
|
||||
return [self stringArrayForKey: @"SOGoContactsDefaultRoles"];
|
||||
@@ -267,6 +277,18 @@
|
||||
return [self stringForKey: @"SOGoVacationFooterTemplateFile"];
|
||||
}
|
||||
|
||||
- (BOOL) vacationAllowZeroDays
|
||||
{
|
||||
id o;
|
||||
|
||||
o = [self stringForKey: @"SOGoVacationAllowZeroDays"];
|
||||
|
||||
if (o)
|
||||
return [o boolValue];
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *) mailingMechanism
|
||||
{
|
||||
NSString *mailingMechanism;
|
||||
|
||||
@@ -1531,6 +1531,11 @@ static NSArray *childRecordFields = nil;
|
||||
return valid;
|
||||
}
|
||||
|
||||
- (NSCalendarDate *) _getMaxStartDate
|
||||
{
|
||||
return nil;
|
||||
}
|
||||
|
||||
/**
|
||||
DAV:sync-collection Report
|
||||
https://datatracker.ietf.org/doc/html/rfc6578#section-3.2
|
||||
@@ -1559,9 +1564,10 @@ static NSArray *childRecordFields = nil;
|
||||
propElement = [(NGDOMNodeWithChildren *) documentElement
|
||||
firstElementWithTag: @"prop" inNamespace: XMLNS_WEBDAV];
|
||||
properties = [self parseDAVRequestedProperties: propElement];
|
||||
|
||||
records = [self syncTokenFieldsWithProperties: properties
|
||||
matchingSyncToken: syncToken
|
||||
fromDate: nil
|
||||
fromDate: [self _getMaxStartDate]
|
||||
initialLoad: NO];
|
||||
|
||||
[self _appendComponentProperties: [properties allKeys]
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
@class NSArray;
|
||||
@class NSException;
|
||||
@class NSString;
|
||||
@class NSDictionary;
|
||||
|
||||
@class WOContext;
|
||||
@class SOGoDomainDefaults;
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
#import "NSString+Utilities.h"
|
||||
#import "SOGoStaticAuthenticator.h"
|
||||
#import "SOGoEmptyAuthenticator.h"
|
||||
#import "SOGoWebAuthenticator.h"
|
||||
#import "SOGoSystemDefaults.h"
|
||||
#import "SOGoUser.h"
|
||||
#import "SOGoUserManager.h"
|
||||
@@ -265,7 +266,8 @@
|
||||
inContext: (WOContext *) woContext
|
||||
systemMessage: (BOOL) isSystemMessage
|
||||
{
|
||||
NSString *currentTo, *login, *password;
|
||||
NSString *currentTo, *login, *password, *encryption, *protocol, *server;
|
||||
NSString * smtpAuthMethod;
|
||||
NSDictionary *currentAcount;
|
||||
NSMutableArray *toErrors;
|
||||
NSEnumerator *addresses;
|
||||
@@ -273,6 +275,7 @@
|
||||
NSException *result;
|
||||
NSURL * smtpUrl;
|
||||
SOGoUser* user;
|
||||
SOGoSystemDefaults *sd;
|
||||
BOOL doSmtpAuth;
|
||||
|
||||
result = nil;
|
||||
@@ -288,12 +291,30 @@
|
||||
currentAcount = [[user mailAccounts] objectAtIndex: userId];
|
||||
|
||||
//Check if we do an smtp authentication
|
||||
doSmtpAuth = [authenticationType isEqualToString: @"plain"] && ![authenticator isKindOfClass: [SOGoEmptyAuthenticator class]];
|
||||
smtpAuthMethod = authenticationType;
|
||||
doSmtpAuth = ([smtpAuthMethod isEqualToString: @"plain"] || [smtpAuthMethod isEqualToString: @"xoauth2"])
|
||||
&& ![authenticator isKindOfClass: [SOGoEmptyAuthenticator class]];
|
||||
if(!doSmtpAuth && userId > 0)
|
||||
{
|
||||
doSmtpAuth = [currentAcount objectForKey: @"smtpAuth"] ? [[currentAcount objectForKey: @"smtpAuth"] boolValue] : NO;
|
||||
}
|
||||
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
if(userId == 0 && [sd doesLoginTypeByDomain] && [authenticator isKindOfClass: [SOGoWebAuthenticator class]])
|
||||
{
|
||||
//Check if the authentication depends on the domain, only for webmail requets not for dav...
|
||||
NSString *username, *_domain;
|
||||
NSRange r;
|
||||
|
||||
username = [currentAcount objectForKey: @"userName"];
|
||||
r = [username rangeOfString: @"@"];
|
||||
if (r.location != NSNotFound)
|
||||
{
|
||||
_domain = [username substringFromIndex: r.location+1];
|
||||
smtpAuthMethod = [sd getSmtpAuthMechForDomain: _domain];
|
||||
}
|
||||
}
|
||||
|
||||
NS_DURING
|
||||
{
|
||||
[client connect];
|
||||
@@ -304,6 +325,7 @@
|
||||
{
|
||||
login = [currentAcount objectForKey: @"userName"];
|
||||
password = [currentAcount objectForKey: @"password"];
|
||||
smtpAuthMethod = "plain"; //Only support plain for auxiliary account
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -315,15 +337,27 @@
|
||||
getExternalLoginForUID: [[authenticator userInContext: woContext] loginInDomain]
|
||||
inDomain: [[authenticator userInContext: woContext] domain]];
|
||||
|
||||
password = [authenticator passwordInContext: woContext];
|
||||
encryption = [currentAcount objectForKey: @"encryption"];
|
||||
protocol = @"imap";
|
||||
if ([encryption isEqualToString: @"ssl"] || [encryption isEqualToString: @"tls"])
|
||||
protocol = @"imaps";
|
||||
server = [NSString stringWithFormat: @"%@://%@", protocol, [currentAcount objectForKey: @"serverName"]];
|
||||
if([authenticator isKindOfClass: [SOGoWebAuthenticator class]])
|
||||
password = [authenticator smtpPasswordInContext: woContext forURL: server];
|
||||
else
|
||||
{
|
||||
password = [authenticator passwordInContext: woContext];
|
||||
smtpAuthMethod = @"plain"; //is a dav or activesync request, only support plain method
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (isSystemMessage
|
||||
&& ![[[SOGoUserManager sharedUserManager] getEmailForUID: [[authenticator userInContext: woContext] loginInDomain]] isEqualToString: sender]
|
||||
&& smtpMasterUserEnabled) {
|
||||
if (![client plainAuthenticateUser: smtpMasterUserUsername
|
||||
withPassword: smtpMasterUserPassword]) {
|
||||
if (![client authenticateUser: smtpMasterUserUsername
|
||||
withPassword: smtpMasterUserPassword
|
||||
withMethod: smtpAuthMethod]) {
|
||||
result = [NSException exceptionWithHTTPStatus: 500
|
||||
reason: @"cannot send message:"
|
||||
@" (smtp) authentication failure"];
|
||||
@@ -334,8 +368,9 @@
|
||||
{
|
||||
if ([login length] == 0
|
||||
|| [login isEqualToString: @"anonymous"]
|
||||
|| ![client plainAuthenticateUser: login
|
||||
withPassword: password])
|
||||
|| ![client authenticateUser: login
|
||||
withPassword: password
|
||||
withMethod: smtpAuthMethod])
|
||||
result = [NSException exceptionWithHTTPStatus: 500
|
||||
reason: @"cannot send message:"
|
||||
@" (smtp) authentication failure"];
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
/* SOGoCASSession.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2000-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 SOGOOPENIDSESSION_H
|
||||
#define SOGOOPENIDSESSION_H
|
||||
|
||||
/* implementation of the OpenId Connect as required for a client/proxy:
|
||||
https://openid.net/developers/how-connect-works/ */
|
||||
|
||||
#import <NGObjWeb/WOResponse.h>
|
||||
#import <SOGo/SOGoObject.h>
|
||||
|
||||
|
||||
@class NSString;
|
||||
@class NSMutableDictionary;
|
||||
@class NSURL;
|
||||
@class NSJSONSerialization;
|
||||
|
||||
|
||||
@interface SOGoOpenIdSession : SOGoObject
|
||||
{
|
||||
//For cache
|
||||
BOOL cacheUpdateNeeded;
|
||||
int userTokenInterval;
|
||||
|
||||
//From sogo.conf
|
||||
BOOL openIdSessionIsOK;
|
||||
NSString *openIdConfigUrl;
|
||||
NSString *openIdScope;
|
||||
NSString *openIdClient;
|
||||
NSString *openIdClientSecret;
|
||||
NSString *openIdEmailParam;
|
||||
BOOL openIdEnableRefreshToken;
|
||||
BOOL sendDomainInfo;
|
||||
|
||||
NSString *forDomain;
|
||||
|
||||
|
||||
//From request to well-known/configuration
|
||||
NSString *authorizationEndpoint;
|
||||
NSString *tokenEndpoint;
|
||||
NSString *introspectionEndpoint;
|
||||
NSString *userinfoEndpoint;
|
||||
NSString *endSessionEndpoint;
|
||||
NSString *revocationEndpoint;
|
||||
|
||||
//Access token
|
||||
NSString *accessToken;
|
||||
NSString *refreshToken;
|
||||
NSString *idToken;
|
||||
NSString *tokenType;
|
||||
NSNumber *expiresIn;
|
||||
NSNumber *refreshExpiresIn;
|
||||
}
|
||||
|
||||
+ (BOOL) checkUserConfig;
|
||||
+ (SOGoOpenIdSession *) OpenIdSession: (NSString *) _domain;
|
||||
+ (SOGoOpenIdSession *) OpenIdSessionWithConfig: (NSDictionary *) _config;
|
||||
+ (SOGoOpenIdSession *) OpenIdSessionWithToken: (NSString *) token domain: (NSString *) _domain;
|
||||
+ (SOGoOpenIdSession *) OpenIdSessionWithTokenAndConfig: (NSString *) token config: (NSDictionary *) _config;
|
||||
+ (void) deleteValueForSessionKey: (NSString *) theSessionKey;
|
||||
|
||||
- (void) initialize;
|
||||
- (void) initializeWithConfig: (NSDictionary *) _config;
|
||||
- (BOOL) sessionIsOK;
|
||||
- (WOResponse *) _performOpenIdRequest: (NSString *) endpoint
|
||||
method: (NSString *) method
|
||||
headers: (NSDictionary *) headers
|
||||
body: (NSData *) body;
|
||||
- (NSMutableDictionary *) fecthConfiguration: (NSString *) _domain;
|
||||
- (void) setAccessToken;
|
||||
- (NSString *) getRefreshToken;
|
||||
- (NSString *) getToken;
|
||||
- (NSString *) getCurrentToken;
|
||||
- (NSString *) loginUrl: (NSString *) oldLocation;
|
||||
- (NSMutableDictionary *) fetchToken: (NSString *) code redirect: (NSString *) oldLocation;
|
||||
|
||||
- (NSString *) logoutUrl;
|
||||
- (NSMutableDictionary *) fetchUserInfo;
|
||||
- (NSString *) _login;
|
||||
- (NSString *) login: (NSString *) identifier;
|
||||
|
||||
|
||||
@end
|
||||
|
||||
|
||||
#endif /* SOGOOPENIDSESSION_H */
|
||||
@@ -0,0 +1,785 @@
|
||||
/* SOGoCASSession.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2010-2014 Inverse inc.
|
||||
*
|
||||
* 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 <NGObjWeb/WOHTTPConnection.h>
|
||||
#import <NGObjWeb/WORequest.h>
|
||||
#import <NGObjWeb/WOResponse.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
|
||||
#import <SOGo/SOGoUser.h>
|
||||
|
||||
|
||||
#import <GDLContentStore/GCSOpenIdFolder.h>
|
||||
#import <GDLContentStore/GCSFolderManager.h>
|
||||
|
||||
#import "NSDictionary+Utilities.h"
|
||||
#import "NSString+Utilities.h"
|
||||
#import "SOGoCache.h"
|
||||
#import "SOGoSystemDefaults.h"
|
||||
|
||||
#import "SOGoOpenIdSession.h"
|
||||
|
||||
static BOOL SOGoOpenIDDebugEnabled = YES;
|
||||
|
||||
@implementation SOGoOpenIdSession
|
||||
|
||||
/// Check if all required parameters to set up a OpenIdSession are in sogo.conf
|
||||
|
||||
+ (BOOL) checkUserConfig
|
||||
{
|
||||
SOGoSystemDefaults *sd;
|
||||
|
||||
if(nil == [[GCSFolderManager defaultFolderManager] openIdFolder])
|
||||
{
|
||||
[self errorWithFormat: @"Something wrong with the table defined by OCSOpenIdURL"];
|
||||
return NO;
|
||||
}
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
return ([sd openIdConfigUrl] && [sd openIdScope] && [sd openIdClient] && [sd openIdClientSecret]);
|
||||
}
|
||||
|
||||
- (void) initializeWithConfig: (NSDictionary *) _config
|
||||
{
|
||||
SOGoSystemDefaults *sd;
|
||||
id refreshTokenBool, domainInfo;
|
||||
|
||||
if([_config objectForKey: @"SOGoOpenIdConfigUrl"] &&
|
||||
[_config objectForKey: @"SOGoOpenIdScope"] &&
|
||||
[_config objectForKey: @"SOGoOpenIdClient"] &&
|
||||
[_config objectForKey: @"SOGoOpenIdClientSecret"])
|
||||
{
|
||||
openIdConfigUrl = [_config objectForKey: @"SOGoOpenIdConfigUrl"];
|
||||
openIdScope = [_config objectForKey: @"SOGoOpenIdScope"];
|
||||
openIdClient = [_config objectForKey: @"SOGoOpenIdClient"];
|
||||
openIdClientSecret = [_config objectForKey: @"SOGoOpenIdClientSecret"];
|
||||
openIdEmailParam = [_config objectForKey: @"SOGoOpenIdEmailParam"];
|
||||
|
||||
openIdEnableRefreshToken = NO;
|
||||
refreshTokenBool = [_config objectForKey: @"SOGoOpenIdEnableRefreshToken"];
|
||||
if (refreshTokenBool && [refreshTokenBool respondsToSelector: @selector (boolValue)])
|
||||
openIdEnableRefreshToken = [refreshTokenBool boolValue];
|
||||
|
||||
sendDomainInfo = NO;
|
||||
domainInfo = [_config objectForKey: @"SOGoOpenIdSendDomainInfo"];
|
||||
if (domainInfo && [domainInfo respondsToSelector: @selector (boolValue)])
|
||||
sendDomainInfo = [domainInfo boolValue];
|
||||
|
||||
|
||||
userTokenInterval = [_config objectForKey: @"SOGoOpenIdTokenCheckInterval"];
|
||||
[self _loadSessionFromCache: forDomain];
|
||||
|
||||
if(cacheUpdateNeeded)
|
||||
{
|
||||
[self fecthConfiguration: forDomain];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"Missing parameters from sogo.conf"];
|
||||
}
|
||||
}
|
||||
|
||||
- (void) initialize: (NSString*) _domain
|
||||
{
|
||||
SOGoSystemDefaults *sd;
|
||||
NSDictionary *config;
|
||||
NSString *type;
|
||||
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
SOGoOpenIDDebugEnabled = [sd openIdDebugEnabled];
|
||||
openIdSessionIsOK = NO;
|
||||
|
||||
//Check if there is a root config or config per domain
|
||||
if(_domain != nil && [sd doesLoginTypeByDomain])
|
||||
{
|
||||
forDomain = _domain;
|
||||
type = [sd getLoginTypeForDomain: _domain];
|
||||
if(type != nil && [type isEqualToString: @"openid"])
|
||||
{
|
||||
config = [sd getLoginConfigForDomain: _domain];
|
||||
[self initializeWithConfig: config];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"Missing parameters from sogo.conf"];
|
||||
}
|
||||
}
|
||||
else if ([[self class] checkUserConfig])
|
||||
{
|
||||
openIdConfigUrl = [sd openIdConfigUrl];
|
||||
openIdScope = [sd openIdScope];
|
||||
openIdClient = [sd openIdClient];
|
||||
openIdClientSecret = [sd openIdClientSecret];
|
||||
openIdEmailParam = [sd openIdEmailParam];
|
||||
openIdEnableRefreshToken = [sd openIdEnableRefreshToken];
|
||||
userTokenInterval = [sd openIdTokenCheckInterval];
|
||||
sendDomainInfo = [sd openIdSendDomainInfo];
|
||||
forDomain = _domain;
|
||||
|
||||
[self _loadSessionFromCache: _domain];
|
||||
|
||||
if(cacheUpdateNeeded)
|
||||
{
|
||||
[self fecthConfiguration: _domain];
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"Missing parameters from sogo.conf"];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
- (WOResponse *) _performOpenIdRequest: (NSString *) endpoint
|
||||
method: (NSString *) method
|
||||
headers: (NSDictionary *) headers
|
||||
body: (NSData *) body
|
||||
{
|
||||
NSURL *url;
|
||||
NSUInteger status;
|
||||
WORequest *request;
|
||||
WOResponse *response;
|
||||
WOHTTPConnection *httpConnection;
|
||||
|
||||
|
||||
url = [NSURL URLWithString: endpoint];
|
||||
if (url)
|
||||
{
|
||||
if(SOGoOpenIDDebugEnabled)
|
||||
{
|
||||
NSLog(@"OpenId perform request: %@ %@", method, endpoint);
|
||||
NSLog(@"OpenId perform request, headers %@", headers);
|
||||
// if(body)
|
||||
// NSLog(@"OpenId perform request: content %@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]);
|
||||
}
|
||||
|
||||
httpConnection = [[WOHTTPConnection alloc] initWithURL: url];
|
||||
[httpConnection autorelease];
|
||||
|
||||
request = [[WORequest alloc] initWithMethod: method
|
||||
uri: [endpoint hostlessURL]
|
||||
httpVersion: @"HTTP/1.1"
|
||||
headers: headers content: body
|
||||
userInfo: nil];
|
||||
[request autorelease];
|
||||
[httpConnection sendRequest: request];
|
||||
response = [httpConnection readResponse];
|
||||
status = [response status];
|
||||
if(status >= 200 && status <500 && status != 404)
|
||||
return response;
|
||||
else if (status == 404)
|
||||
{
|
||||
[self errorWithFormat: @"OpenID endpoint not found (404): %@", endpoint];
|
||||
return nil;
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"OpenID server internal error during %@: %@", endpoint, response];
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[self errorWithFormat: @"OpenID can't handle endpoint (not a url): '%@'", endpoint];
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *) fecthConfiguration: (NSString*) _domain
|
||||
{
|
||||
NSString *content;
|
||||
WOResponse * response;
|
||||
NSUInteger status;
|
||||
NSMutableDictionary *result;
|
||||
NSDictionary *config, *headers;
|
||||
NSURL *url;
|
||||
|
||||
result = [NSMutableDictionary dictionary];
|
||||
[result setObject: self->openIdConfigUrl forKey: @"url"];
|
||||
|
||||
url = [NSURL URLWithString: self->openIdConfigUrl ];
|
||||
if (url)
|
||||
{
|
||||
if(self->sendDomainInfo && _domain != nil && [_domain length] > 0)
|
||||
headers = [NSDictionary dictionaryWithObject: _domain forKey: @"sogo-user-domain"];
|
||||
else
|
||||
headers = nil;
|
||||
|
||||
response = [self _performOpenIdRequest: self->openIdConfigUrl
|
||||
method: @"GET"
|
||||
headers: headers
|
||||
body: nil];
|
||||
|
||||
if (response)
|
||||
{
|
||||
status = [response status];
|
||||
if(status >= 200 && status <300)
|
||||
{
|
||||
content = [response contentString];
|
||||
config = [content objectFromJSONString];
|
||||
self->authorizationEndpoint = [config objectForKey: @"authorization_endpoint"];
|
||||
self->tokenEndpoint = [config objectForKey: @"token_endpoint"];
|
||||
self->userinfoEndpoint = [config objectForKey: @"userinfo_endpoint"];
|
||||
self->endSessionEndpoint = [config objectForKey: @"end_session_endpoint"];
|
||||
|
||||
//Optionnals?
|
||||
if([config objectForKey: @"introspection_endpoint"])
|
||||
self->introspectionEndpoint = [config objectForKey: @"introspection_endpoint"];
|
||||
if([config objectForKey: @"revocation_endpoint"])
|
||||
self->revocationEndpoint = [config objectForKey: @"revocation_endpoint"];
|
||||
|
||||
openIdSessionIsOK = YES;
|
||||
[self _saveSessionToCache: _domain];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"Error during fetching the configuration (status %d), response: %@", status, response];
|
||||
}
|
||||
}
|
||||
else
|
||||
[result setObject: @"http-error" forKey: @"error"];
|
||||
}
|
||||
else
|
||||
[result setObject: @"invalid-url" forKey: @"error"];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
+ (SOGoOpenIdSession *) OpenIdSession: (NSString *) _domain
|
||||
{
|
||||
SOGoOpenIdSession *newSession;
|
||||
|
||||
newSession = [self new];
|
||||
[newSession autorelease];
|
||||
[newSession initialize: _domain];
|
||||
|
||||
return newSession;
|
||||
}
|
||||
|
||||
+ (SOGoOpenIdSession *) OpenIdSessionWithConfig: (NSDictionary *) _config
|
||||
{
|
||||
SOGoOpenIdSession *newSession;
|
||||
|
||||
newSession = [self new];
|
||||
[newSession autorelease];
|
||||
[newSession initializeWithConfig: _config];
|
||||
|
||||
return newSession;
|
||||
}
|
||||
|
||||
+ (SOGoOpenIdSession *) OpenIdSessionWithToken: (NSString *) token domain: (NSString *) _domain
|
||||
{
|
||||
SOGoOpenIdSession *newSession;
|
||||
|
||||
if (token)
|
||||
{
|
||||
newSession = [self new];
|
||||
[newSession autorelease];
|
||||
[newSession initialize: _domain];
|
||||
|
||||
[newSession setAccessToken: token];
|
||||
}
|
||||
else
|
||||
newSession = nil;
|
||||
|
||||
return newSession;
|
||||
}
|
||||
|
||||
+ (SOGoOpenIdSession *) OpenIdSessionWithTokenAndConfig: (NSString *) token config: (NSDictionary *) _config
|
||||
{
|
||||
SOGoOpenIdSession *newSession;
|
||||
|
||||
if (token)
|
||||
{
|
||||
newSession = [self new];
|
||||
[newSession autorelease];
|
||||
[newSession initializeWithConfig: _config];
|
||||
|
||||
[newSession setAccessToken: token];
|
||||
}
|
||||
else
|
||||
newSession = nil;
|
||||
|
||||
return newSession;
|
||||
}
|
||||
|
||||
|
||||
- (BOOL) sessionIsOk
|
||||
{
|
||||
return self->openIdSessionIsOK;
|
||||
}
|
||||
|
||||
- (void) _loadSessionFromCache: (NSString*) _domain
|
||||
{
|
||||
SOGoCache *cache;
|
||||
NSString *jsonSession, *cacheKey;
|
||||
NSDictionary *sessionDict;
|
||||
|
||||
if(_domain != nil && [_domain length] > 0)
|
||||
cacheKey = [self->openIdConfigUrl stringByAppendingFormat: @":%@", _domain];
|
||||
else
|
||||
cacheKey = self->openIdConfigUrl;
|
||||
|
||||
cache = [SOGoCache sharedCache];
|
||||
jsonSession = [cache openIdSessionFromServer: cacheKey];
|
||||
if ([jsonSession length])
|
||||
{
|
||||
sessionDict = [jsonSession objectFromJSONString];
|
||||
ASSIGN (authorizationEndpoint, [sessionDict objectForKey: @"authorization_endpoint"]);
|
||||
ASSIGN (tokenEndpoint, [sessionDict objectForKey: @"token_endpoint"]);
|
||||
ASSIGN (userinfoEndpoint, [sessionDict objectForKey: @"userinfo_endpoint"]);
|
||||
ASSIGN (endSessionEndpoint, [sessionDict objectForKey: @"end_session_endpoint"]);
|
||||
|
||||
//Optionnals?
|
||||
if([sessionDict objectForKey: @"introspection_endpoint"])
|
||||
ASSIGN (introspectionEndpoint, [sessionDict objectForKey: @"introspection_endpoint"]);
|
||||
if([sessionDict objectForKey: @"revocation_endpoint"])
|
||||
ASSIGN (revocationEndpoint, [sessionDict objectForKey: @"revocation_endpoint"]);
|
||||
openIdSessionIsOK = YES;
|
||||
}
|
||||
else
|
||||
cacheUpdateNeeded = YES;
|
||||
}
|
||||
|
||||
- (void) _saveSessionToCache: (NSString*) _domain
|
||||
{
|
||||
SOGoCache *cache;
|
||||
NSString *jsonSession, *cacheKey;
|
||||
NSMutableDictionary *sessionDict;
|
||||
|
||||
cache = [SOGoCache sharedCache];
|
||||
sessionDict = [NSMutableDictionary dictionary];
|
||||
[sessionDict setObject: authorizationEndpoint forKey: @"authorization_endpoint"];
|
||||
[sessionDict setObject: tokenEndpoint forKey: @"token_endpoint"];
|
||||
[sessionDict setObject: userinfoEndpoint forKey: @"userinfo_endpoint"];
|
||||
[sessionDict setObject: endSessionEndpoint forKey: @"end_session_endpoint"];
|
||||
|
||||
//Optionnals?
|
||||
if(introspectionEndpoint)
|
||||
[sessionDict setObject: introspectionEndpoint forKey: @"introspection_endpoint"];
|
||||
if(revocationEndpoint)
|
||||
[sessionDict setObject: revocationEndpoint forKey: @"revocation_endpoint"];
|
||||
|
||||
jsonSession = [sessionDict jsonRepresentation];
|
||||
|
||||
if(_domain != nil && [_domain length] > 0)
|
||||
cacheKey = [self->openIdConfigUrl stringByAppendingFormat: @":%@", _domain];
|
||||
else
|
||||
cacheKey = self->openIdConfigUrl;
|
||||
|
||||
[cache setOpenIdSession: jsonSession
|
||||
forServer: cacheKey];
|
||||
}
|
||||
|
||||
|
||||
- (BOOL) _loadUserFromCache: (NSString *) email
|
||||
{
|
||||
/*
|
||||
Return NO if the user token must be check again, YES the user is valid for now
|
||||
*/
|
||||
SOGoCache *cache;
|
||||
NSString *identifier, *nextCheck;
|
||||
NSTimeInterval nextCheckTime, timeNow;
|
||||
|
||||
identifier = [self->openIdClient stringByAppendingString: email];
|
||||
|
||||
cache = [SOGoCache sharedCache];
|
||||
nextCheck = [cache userOpendIdSessionFromServer: identifier];
|
||||
if ([nextCheck length])
|
||||
{
|
||||
timeNow = [[NSDate date] timeIntervalSince1970];
|
||||
nextCheckTime = [nextCheck doubleValue];
|
||||
if(timeNow > nextCheckTime)
|
||||
return NO;
|
||||
else
|
||||
return YES;
|
||||
}
|
||||
else
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void) _saveUserToCache: (NSString *) email
|
||||
{
|
||||
SOGoCache *cache;
|
||||
NSString *identifier, *nextCheck;
|
||||
NSTimeInterval nextCheckTime;
|
||||
|
||||
identifier = [self->openIdClient stringByAppendingString: email];
|
||||
nextCheckTime = [[NSDate date] timeIntervalSince1970] + self->userTokenInterval;
|
||||
nextCheck = [NSString stringWithFormat:@"%f", nextCheckTime];
|
||||
|
||||
cache = [SOGoCache sharedCache];
|
||||
[cache setUserOpendIdSessionFromServer: identifier
|
||||
nextCheckAfter: nextCheck];
|
||||
}
|
||||
|
||||
- (NSString*) loginUrl: (NSString *) oldLocation
|
||||
{
|
||||
NSString* logUrl;
|
||||
logUrl = [self->authorizationEndpoint stringByAppendingFormat: @"?scope=%@", self->openIdScope];
|
||||
logUrl = [logUrl stringByAppendingString: @"&response_type=code"];
|
||||
logUrl = [logUrl stringByAppendingFormat: @"&client_id=%@", self->openIdClient];
|
||||
logUrl = [logUrl stringByAppendingFormat: @"&redirect_uri=%@", oldLocation];
|
||||
if(self->forDomain != nil && [self->forDomain length] > 0)
|
||||
logUrl = [logUrl stringByAppendingFormat: @"&sogo_domain=%@", forDomain];
|
||||
// logurl = [self->logurl stringByAppendingFormat: @"&state=%@", state];
|
||||
|
||||
return logUrl;
|
||||
}
|
||||
|
||||
- (NSString *) logoutUrl
|
||||
{
|
||||
return self->endSessionEndpoint;
|
||||
}
|
||||
|
||||
- (NSString*) getRefreshToken
|
||||
{
|
||||
return self->refreshToken;
|
||||
}
|
||||
|
||||
- (NSString*) getToken
|
||||
{
|
||||
return self->accessToken;
|
||||
}
|
||||
|
||||
- (void) setAccessToken: (NSString* ) token
|
||||
{
|
||||
self->accessToken = token;
|
||||
}
|
||||
|
||||
|
||||
- (NSString *) getCurrentToken
|
||||
{
|
||||
NSString *currentToken;
|
||||
|
||||
//See if the current token has been refreshed
|
||||
currentToken = [[[GCSFolderManager defaultFolderManager] openIdFolder] getNewToken: self->accessToken];
|
||||
|
||||
if(currentToken)
|
||||
{
|
||||
//be sure the old token has been cleaned
|
||||
[[[GCSFolderManager defaultFolderManager] openIdFolder] deleteOpenIdSessionFor: self->accessToken];
|
||||
return currentToken;
|
||||
}
|
||||
return self->accessToken;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *) fetchToken: (NSString * ) code redirect: (NSString *) oldLocation
|
||||
{
|
||||
NSString *location, *form, *content;
|
||||
WOResponse *response;
|
||||
NSUInteger status;
|
||||
NSMutableDictionary *result;
|
||||
NSDictionary *headers;
|
||||
NSDictionary *tokenRet;
|
||||
NSURL *url;
|
||||
|
||||
result = [NSMutableDictionary dictionary];
|
||||
[result setObject: @"ok" forKey: @"error"];
|
||||
|
||||
location = self->tokenEndpoint;
|
||||
url = [NSURL URLWithString: location];
|
||||
if (url)
|
||||
{
|
||||
form = @"grant_type=authorization_code";
|
||||
form = [form stringByAppendingFormat: @"&code=%@", code];
|
||||
form = [form stringByAppendingFormat: @"&redirect_uri=%@", [oldLocation stringByEscapingURL]];
|
||||
form = [form stringByAppendingFormat: @"&client_secret=%@", self->openIdClientSecret];
|
||||
form = [form stringByAppendingFormat: @"&client_id=%@", self->openIdClient];
|
||||
|
||||
if(self->sendDomainInfo && self->forDomain != nil && [self->forDomain length] > 0)
|
||||
headers = [NSDictionary dictionaryWithObjectsAndKeys: @"application/x-www-form-urlencoded", @"content-type",
|
||||
self->forDomain, @"sogo-user-domain", nil];
|
||||
else
|
||||
headers = [NSDictionary dictionaryWithObject: @"application/x-www-form-urlencoded" forKey: @"content-type"];
|
||||
|
||||
response = [self _performOpenIdRequest: location
|
||||
method: @"POST"
|
||||
headers: headers
|
||||
body: [form dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
|
||||
if (response)
|
||||
{
|
||||
status = [response status];
|
||||
if(status >= 200 && status <300)
|
||||
{
|
||||
content = [response contentString];
|
||||
tokenRet = [content objectFromJSONString];
|
||||
if (SOGoOpenIDDebugEnabled)
|
||||
{
|
||||
NSLog(@"fetch token response: %@", tokenRet);
|
||||
}
|
||||
self->accessToken = [tokenRet objectForKey: @"access_token"];
|
||||
self->refreshToken = [tokenRet objectForKey: @"refresh_token"];
|
||||
self->refreshExpiresIn = [tokenRet objectForKey: @"refresh_expires_in"];
|
||||
self->idToken = [tokenRet objectForKey: @"id_token"];
|
||||
self->tokenType = [tokenRet objectForKey: @"token_type"];
|
||||
self->expiresIn = [tokenRet objectForKey: @"expires_in"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"Error during fetching the token (status %d), response: %@", status, response];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[result setObject: @"http-error" forKey: @"error"];
|
||||
}
|
||||
}
|
||||
else
|
||||
[result setObject: @"invalid-url" forKey: @"error"];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *) refreshToken: (NSString * ) userRefreshToken
|
||||
{
|
||||
NSString *location, *form, *content;
|
||||
WOResponse *response;
|
||||
NSUInteger status;
|
||||
NSMutableDictionary *result;
|
||||
NSDictionary *headers;
|
||||
NSDictionary *refreshTokenRet;
|
||||
NSURL *url;
|
||||
|
||||
result = [NSMutableDictionary dictionary];
|
||||
[result setObject: @"ok" forKey: @"error"];
|
||||
|
||||
if(!userRefreshToken || [userRefreshToken length] == 0)
|
||||
{
|
||||
[result setObject: @"invalid-token" forKey: @"error"];
|
||||
return result;
|
||||
}
|
||||
|
||||
location = self->tokenEndpoint;
|
||||
url = [NSURL URLWithString: location];
|
||||
if (url)
|
||||
{
|
||||
form = @"grant_type=refresh_token";
|
||||
form = [form stringByAppendingFormat: @"&scope=%@", self->openIdScope];
|
||||
form = [form stringByAppendingFormat: @"&refresh_token=%@", userRefreshToken];
|
||||
form = [form stringByAppendingFormat: @"&client_secret=%@", self->openIdClientSecret];
|
||||
form = [form stringByAppendingFormat: @"&client_id=%@", self->openIdClient];
|
||||
|
||||
if(self->sendDomainInfo && self->forDomain != nil && [self->forDomain length] > 0)
|
||||
headers = [NSDictionary dictionaryWithObjectsAndKeys: @"application/x-www-form-urlencoded", @"content-type",
|
||||
self->forDomain, @"sogo-user-domain", nil];
|
||||
else
|
||||
headers = [NSDictionary dictionaryWithObject: @"application/x-www-form-urlencoded" forKey: @"content-type"];
|
||||
|
||||
response = [self _performOpenIdRequest: location
|
||||
method: @"POST"
|
||||
headers: headers
|
||||
body: [form dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
|
||||
if (response)
|
||||
{
|
||||
status = [response status];
|
||||
if(status >= 200 && status <300)
|
||||
{
|
||||
content = [response contentString];
|
||||
refreshTokenRet = [content objectFromJSONString];
|
||||
if (SOGoOpenIDDebugEnabled)
|
||||
{
|
||||
NSLog(@"refresh token response: %@", refreshTokenRet);
|
||||
}
|
||||
self->accessToken = [refreshTokenRet objectForKey: @"access_token"];
|
||||
self->refreshToken = [refreshTokenRet objectForKey: @"refresh_token"];
|
||||
self->refreshExpiresIn = [refreshTokenRet objectForKey: @"refresh_expires_in"];
|
||||
self->tokenType = [refreshTokenRet objectForKey: @"token_type"];
|
||||
self->expiresIn = [refreshTokenRet objectForKey: @"expires_in"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"Error during refreshing the token (status %d), response: %@", status, response];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[result setObject: @"http-error" forKey: @"error"];
|
||||
}
|
||||
}
|
||||
else
|
||||
[result setObject: @"invalid-url" forKey: @"error"];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSMutableDictionary *) fetchUserInfo
|
||||
{
|
||||
NSString *location, *auth, *content;
|
||||
WOResponse *response;
|
||||
NSUInteger status;
|
||||
NSMutableDictionary *result;
|
||||
NSDictionary *profile, *headers;
|
||||
NSURL *url;
|
||||
NSString *email;
|
||||
|
||||
result = [NSMutableDictionary dictionary];
|
||||
[result setObject: @"ok" forKey: @"error"];
|
||||
|
||||
location = self->userinfoEndpoint;
|
||||
url = [NSURL URLWithString: location];
|
||||
if (url)
|
||||
{
|
||||
auth = [NSString stringWithFormat: @"Bearer %@", self->accessToken];
|
||||
if(self->sendDomainInfo && self->forDomain != nil && [self->forDomain length] > 0)
|
||||
headers = [NSDictionary dictionaryWithObjectsAndKeys: @"application/x-www-form-urlencoded", @"content-type",
|
||||
self->forDomain, @"sogo-user-domain",
|
||||
auth, @"authorization", nil];
|
||||
else
|
||||
headers = [NSDictionary dictionaryWithObjectsAndKeys: @"application/x-www-form-urlencoded", @"content-type",
|
||||
auth, @"authorization", nil];
|
||||
|
||||
response = [self _performOpenIdRequest: location
|
||||
method: @"GET"
|
||||
headers: headers
|
||||
body: nil];
|
||||
if (response)
|
||||
{
|
||||
status = [response status];
|
||||
if(status >= 200 && status <300)
|
||||
{
|
||||
content = [response contentString];
|
||||
profile = [content objectFromJSONString];
|
||||
if(SOGoOpenIDDebugEnabled && profile)
|
||||
NSLog(@"OpenId fetch user info, profile is %@", profile);
|
||||
|
||||
/*profile = {"sub":"70a3e6a1-37cf-4cf6-b114-6973aabca86a",
|
||||
"email_verified":false,
|
||||
"address":{},
|
||||
"name":"Foo Bar",
|
||||
"preferred_username":"myuser",
|
||||
"given_name":"Foo",
|
||||
"family_name":"Bar",
|
||||
"email":"myuser@user.com"}*/
|
||||
if (email = [profile objectForKey: self->openIdEmailParam])
|
||||
{
|
||||
if(self->userTokenInterval > 0)
|
||||
[self _saveUserToCache: email];
|
||||
[result setObject: email forKey: @"login"];
|
||||
}
|
||||
else
|
||||
[result setObject: @"no mail found" forKey: @"error"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"Error during fetching the token (status %d), response: %@", status, response];
|
||||
[result setObject: @"http-error" forKey: @"error"];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[result setObject: @"http-error" forKey: @"error"];
|
||||
}
|
||||
}
|
||||
else
|
||||
[result setObject: @"invalid-url" forKey: @"error"];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+ (void) deleteValueForSessionKey: (NSString *) theSessionKey
|
||||
{
|
||||
GCSOpenIdFolder *folder;
|
||||
|
||||
folder = [[GCSFolderManager defaultFolderManager] openIdFolder];
|
||||
[folder deleteOpenIdSessionFor: theSessionKey];
|
||||
}
|
||||
|
||||
- (NSString *) _login
|
||||
{
|
||||
NSMutableDictionary *resultUserInfo, *resultResfreh;
|
||||
NSString* _oldAccessToken;
|
||||
|
||||
resultUserInfo = [self fetchUserInfo];
|
||||
if ([[resultUserInfo objectForKey: @"error"] isEqualToString: @"ok"])
|
||||
{
|
||||
//Save some infos as the refresh token to the database
|
||||
[[[GCSFolderManager defaultFolderManager] openIdFolder] writeOpenIdSession: self->accessToken
|
||||
withOldSession: nil
|
||||
withRefreshToken: self->refreshToken
|
||||
withExpire: self->expiresIn
|
||||
withRefreshExpire: self->refreshExpiresIn];
|
||||
return [resultUserInfo objectForKey: @"login"];
|
||||
}
|
||||
else if(openIdEnableRefreshToken)
|
||||
{
|
||||
//try to refresh
|
||||
if(self->accessToken)
|
||||
{
|
||||
self->refreshToken = [[[GCSFolderManager defaultFolderManager] openIdFolder] getRefreshToken: self->accessToken];
|
||||
//remove old session
|
||||
[[[GCSFolderManager defaultFolderManager] openIdFolder] deleteOpenIdSessionFor: self->accessToken];
|
||||
}
|
||||
if(self->refreshToken)
|
||||
{
|
||||
_oldAccessToken = self->accessToken;
|
||||
resultResfreh = [self refreshToken: self->refreshToken];
|
||||
if ([[resultResfreh objectForKey: @"error"] isEqualToString: @"ok"])
|
||||
{
|
||||
resultUserInfo = [self fetchUserInfo];
|
||||
if ([[resultUserInfo objectForKey: @"error"] isEqualToString: @"ok"])
|
||||
{
|
||||
[[[GCSFolderManager defaultFolderManager] openIdFolder] writeOpenIdSession: self->accessToken
|
||||
withOldSession: _oldAccessToken
|
||||
withRefreshToken: self->refreshToken
|
||||
withExpire: self->expiresIn
|
||||
withRefreshExpire: self->refreshExpiresIn];
|
||||
return [resultUserInfo objectForKey: @"login"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//The acces token hasn't work, delete the session in database if needed
|
||||
if(self->accessToken)
|
||||
{
|
||||
[[[GCSFolderManager defaultFolderManager] openIdFolder] deleteOpenIdSessionFor: self->accessToken];
|
||||
}
|
||||
|
||||
[self errorWithFormat: @"Can't get user email from profile because: %@", [resultUserInfo objectForKey: @"error"]];
|
||||
}
|
||||
else
|
||||
{
|
||||
//remove old session
|
||||
[[[GCSFolderManager defaultFolderManager] openIdFolder] deleteOpenIdSessionFor: self->accessToken];
|
||||
}
|
||||
|
||||
return @"anonymous";
|
||||
}
|
||||
|
||||
- (NSString *) login: (NSString *) email
|
||||
{
|
||||
//Check if we need to fetch userinfo
|
||||
if(self->userTokenInterval > 0 && [self _loadUserFromCache: email])
|
||||
{
|
||||
return email;
|
||||
}
|
||||
else
|
||||
{
|
||||
return [self _login];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -247,12 +247,20 @@
|
||||
usingKey: theKey];
|
||||
|
||||
r = [decodedValue rangeOfString: @":"];
|
||||
*theLogin = [decodedValue substringToIndex: r.location];
|
||||
*thePassword = [decodedValue substringFromIndex: r.location+1];
|
||||
if (r.location != NSNotFound)
|
||||
{
|
||||
*theLogin = [decodedValue substringToIndex: r.location];
|
||||
*thePassword = [decodedValue substringFromIndex: r.location+1];
|
||||
}
|
||||
else
|
||||
{
|
||||
*theLogin = nil;
|
||||
*thePassword = nil;
|
||||
}
|
||||
*theDomain = nil;
|
||||
|
||||
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
if ([sd enableDomainBasedUID])
|
||||
if (*theLogin &&[sd enableDomainBasedUID])
|
||||
{
|
||||
r = [*theLogin rangeOfString: @"@" options: NSBackwardsSearch];
|
||||
if (r.location != NSNotFound)
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#import <SOGo/NSDictionary+Utilities.h>
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
#import <SOGo/SOGoDomainDefaults.h>
|
||||
#import <SOGo/SOGoSystemDefaults.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoTextTemplateFile.h>
|
||||
|
||||
@@ -697,8 +698,11 @@ static NSString *sieveScriptName = @"sogo";
|
||||
NSDictionary *result;
|
||||
NSString *login, *authname, *password;
|
||||
SOGoDomainDefaults *dd;
|
||||
SOGoSystemDefaults *sd;
|
||||
NGSieveClient *client;
|
||||
NSString *sieveServer, *sieveScheme, *sieveQuery, *imapServer;
|
||||
NSString *imapAuthMech, *userDomain;
|
||||
NSRange r;
|
||||
NSURL *url, *cUrl;
|
||||
int sievePort;
|
||||
BOOL connected;
|
||||
@@ -773,7 +777,20 @@ static NSString *sieveScriptName = @"sogo";
|
||||
url = [NSURL URLWithString: [NSString stringWithFormat: @"%@://%@:%d%@",
|
||||
sieveScheme, sieveServer, sievePort, sieveQuery]];
|
||||
|
||||
client = [[NGSieveClient alloc] initWithURL: url];
|
||||
//In case of differrent auth method for different domain, check it
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
imapAuthMech = nil;
|
||||
if([sd doesLoginTypeByDomain])
|
||||
{
|
||||
r = [theUsername rangeOfString: @"@"];
|
||||
if (r.location != NSNotFound)
|
||||
{
|
||||
userDomain = [theUsername substringFromIndex: r.location+1];
|
||||
imapAuthMech = [sd getImapAuthMechForDomain: userDomain];
|
||||
}
|
||||
}
|
||||
|
||||
client = [[NGSieveClient alloc] initWithURL: url andAuthMech: imapAuthMech];
|
||||
|
||||
if (!client) {
|
||||
[self errorWithFormat: @"Sieve connection failed on %@", [url description]];
|
||||
@@ -1016,8 +1033,8 @@ static NSString *sieveScriptName = @"sogo";
|
||||
text = [NSString stringWithFormat: @"%@%@", text, [templateFile textForUser: user]];
|
||||
}
|
||||
|
||||
if (days == 0)
|
||||
days = 7;
|
||||
if (days <= 0)
|
||||
days = [dd vacationAllowZeroDays] ? 0 : 7;
|
||||
|
||||
vacation_script = [NSMutableString string];
|
||||
|
||||
|
||||
@@ -36,6 +36,11 @@ static const NSString *kDisableSharingCalendar = @"Calendar";
|
||||
+ (SOGoSystemDefaults *) sharedSystemDefaults;
|
||||
|
||||
- (NSArray *) domainIds;
|
||||
- (BOOL) doesLoginTypeByDomain;
|
||||
- (NSString *) getLoginTypeForDomain: (NSString*) _domain;
|
||||
- (NSString *) getLoginConfigForDomain: (NSDictionary*) _domain;
|
||||
- (NSString *) getImapAuthMechForDomain: (NSString*) _domain;
|
||||
- (NSString *) getSmtpAuthMechForDomain: (NSString*) _domain;
|
||||
- (BOOL) forbidUnknownDomainsAuth;
|
||||
- (NSArray *) domainsAllowed;
|
||||
- (BOOL) enableDomainBasedUID;
|
||||
@@ -55,6 +60,7 @@ static const NSString *kDisableSharingCalendar = @"Calendar";
|
||||
|
||||
- (BOOL) isWebAccessEnabled;
|
||||
- (BOOL) isCalendarDAVAccessEnabled;
|
||||
- (BOOL) isCalendarJitsiLinkEnabled;
|
||||
- (BOOL) isAddressBookDAVAccessEnabled;
|
||||
|
||||
- (BOOL) enableEMailAlarms;
|
||||
@@ -76,6 +82,7 @@ static const NSString *kDisableSharingCalendar = @"Calendar";
|
||||
|
||||
- (BOOL) uixDebugEnabled;
|
||||
- (BOOL) easDebugEnabled;
|
||||
- (BOOL) openIdDebugEnabled;
|
||||
- (BOOL) tnefDecoderDebugEnabled;
|
||||
- (BOOL) xsrfValidationEnabled;
|
||||
|
||||
@@ -88,11 +95,22 @@ NSComparisonResult languageSort(id el1, id el2, void *context);
|
||||
- (NSString *) loginSuffix;
|
||||
|
||||
- (NSString *) authenticationType;
|
||||
- (BOOL) isSsoUsed: (NSString *) domain;
|
||||
- (NSString *) davAuthenticationType;
|
||||
|
||||
- (NSString *) CASServiceURL;
|
||||
- (BOOL) CASLogoutEnabled;
|
||||
|
||||
- (NSString *) openIdConfigUrl;
|
||||
- (NSString *) openIdScope;
|
||||
- (NSString *) openIdClient;
|
||||
- (NSString *) openIdClientSecret;
|
||||
- (NSString *) openIdEmailParam;
|
||||
- (BOOL) openIdEnableRefreshToken;
|
||||
- (BOOL) openIdLogoutEnabled: (NSString *) _domain;
|
||||
- (int) openIdTokenCheckInterval;
|
||||
- (BOOL) openIdSendDomainInfo;
|
||||
|
||||
- (NSString *) SAML2PrivateKeyLocation;
|
||||
- (NSString *) SAML2CertificateLocation;
|
||||
- (NSString *) SAML2IdpMetadataLocation;
|
||||
@@ -140,6 +158,10 @@ NSComparisonResult languageSort(id el1, id el2, void *context);
|
||||
- (BOOL)isURLEncryptionEnabled;
|
||||
- (NSString *)urlEncryptionPassphrase;
|
||||
|
||||
- (NSString *)urlCreateAccount;
|
||||
|
||||
- (BOOL)enableMailCleaning;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* SOGOSYSTEMDEFAULTS_H */
|
||||
@@ -262,6 +262,143 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict,
|
||||
return [domains allKeys];
|
||||
}
|
||||
|
||||
- (BOOL) doesLoginTypeByDomain
|
||||
{
|
||||
return ([self dictionaryForKey: @"SOGoLoginTypeByDomain"] != nil);
|
||||
}
|
||||
|
||||
- (NSString *) getLoginTypeForDomain: (NSString*) _domain
|
||||
{
|
||||
NSDictionary *domains, *config;
|
||||
NSString *type;
|
||||
if(![self doesLoginTypeByDomain])
|
||||
return nil;
|
||||
domains = [self dictionaryForKey: @"SOGoLoginTypeByDomain"];
|
||||
if([domains objectForKey: _domain])
|
||||
{
|
||||
config = [domains objectForKey: _domain];
|
||||
}
|
||||
else if([domains objectForKey: @"login_default"])
|
||||
{
|
||||
config = [domains objectForKey: @"login_default"];
|
||||
}
|
||||
else
|
||||
return nil;
|
||||
|
||||
if((type = [config objectForKey: @"type"]))
|
||||
{
|
||||
return type;
|
||||
}
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *) getImapAuthMechForDomain: (NSString*) _domain
|
||||
{
|
||||
NSDictionary *domains, *config;
|
||||
NSString *type;
|
||||
|
||||
if(![self doesLoginTypeByDomain])
|
||||
return nil;
|
||||
|
||||
domains = [self dictionaryForKey: @"SOGoLoginTypeByDomain"];
|
||||
|
||||
if([domains objectForKey: _domain])
|
||||
{
|
||||
config = [domains objectForKey: _domain];
|
||||
}
|
||||
else if([domains objectForKey: @"login_default"])
|
||||
{
|
||||
config = [domains objectForKey: @"login_default"];
|
||||
}
|
||||
else
|
||||
return nil;
|
||||
|
||||
if((type = [config objectForKey: @"imapAuthMech"]))
|
||||
{
|
||||
return type;
|
||||
}
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *) getSmtpAuthMechForDomain: (NSString*) _domain
|
||||
{
|
||||
NSDictionary *domains, *config;
|
||||
NSString *type;
|
||||
|
||||
if(![self doesLoginTypeByDomain])
|
||||
return nil;
|
||||
|
||||
domains = [self dictionaryForKey: @"SOGoLoginTypeByDomain"];
|
||||
|
||||
if([domains objectForKey: _domain])
|
||||
{
|
||||
config = [domains objectForKey: _domain];
|
||||
}
|
||||
else if([domains objectForKey: @"login_default"])
|
||||
{
|
||||
config = [domains objectForKey: @"login_default"];
|
||||
}
|
||||
else
|
||||
return nil;
|
||||
|
||||
if((type = [config objectForKey: @"smtpAuthMech"]))
|
||||
{
|
||||
return type;
|
||||
}
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *) getLoginConfigForDomain: (NSDictionary*) _domain
|
||||
{
|
||||
NSDictionary *domains, *config;
|
||||
if(![self doesLoginTypeByDomain])
|
||||
return nil;
|
||||
domains = [self dictionaryForKey: @"SOGoLoginTypeByDomain"];
|
||||
if([domains objectForKey: _domain])
|
||||
{
|
||||
config = [domains objectForKey: _domain];
|
||||
}
|
||||
else if([domains objectForKey: @"login_default"])
|
||||
{
|
||||
config = [domains objectForKey: @"login_default"];
|
||||
}
|
||||
|
||||
if(config)
|
||||
return config;
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (BOOL) hasOpenIdType
|
||||
{
|
||||
if([self doesLoginTypeByDomain])
|
||||
{
|
||||
NSDictionary *domainsConfig;
|
||||
NSEnumerator *e;
|
||||
NSString *domain, *type;
|
||||
if(![self doesLoginTypeByDomain])
|
||||
return NO;
|
||||
domainsConfig = [self dictionaryForKey: @"SOGoLoginTypeByDomain"];
|
||||
e = [domainsConfig keyEnumerator];
|
||||
while((domain = [e nextObject]))
|
||||
{
|
||||
if((type = [[domainsConfig objectForKey: domain] objectForKey: @"type"]))
|
||||
{
|
||||
if([type isEqualToString: @"openid"])
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
else
|
||||
return [[self authenticationType] isEqualToString: @"openid"];
|
||||
|
||||
}
|
||||
|
||||
|
||||
- (BOOL) enableDomainBasedUID
|
||||
{
|
||||
return [self boolForKey: @"SOGoEnableDomainBasedUID"];
|
||||
@@ -441,6 +578,11 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict,
|
||||
return [self boolForKey: @"SOGoCalendarDAVAccessEnabled"];
|
||||
}
|
||||
|
||||
- (BOOL) isCalendarJitsiLinkEnabled
|
||||
{
|
||||
return [self boolForKey: @"SOGoCalendarEnableJitsiLink"];
|
||||
}
|
||||
|
||||
- (BOOL) isAddressBookDAVAccessEnabled
|
||||
{
|
||||
return [self boolForKey: @"SOGoAddressBookDAVAccessEnabled"];
|
||||
@@ -507,6 +649,11 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict,
|
||||
return [self boolForKey: @"SOGoEASDebugEnabled"];
|
||||
}
|
||||
|
||||
- (BOOL) openIdDebugEnabled
|
||||
{
|
||||
return [self boolForKey: @"SOGoOpenIDDebugEnabled"];
|
||||
}
|
||||
|
||||
- (BOOL) tnefDecoderDebugEnabled
|
||||
{
|
||||
return [self boolForKey: @"SOGoTnefDecoderDebugEnabled"];
|
||||
@@ -577,6 +724,16 @@ NSComparisonResult languageSort(id el1, id el2, void *context)
|
||||
return [[self stringForKey: @"SOGoAuthenticationType"] lowercaseString];
|
||||
}
|
||||
|
||||
- (BOOL) isSsoUsed: (NSString *) domain
|
||||
{
|
||||
NSString* authType;
|
||||
|
||||
authType = [self getLoginTypeForDomain: domain];
|
||||
if(!authType)
|
||||
authType = [self authenticationType];
|
||||
return ([authType isEqualToString: @"cas"] || [authType isEqualToString: @"saml2"] || [authType isEqualToString: @"openid"]);
|
||||
}
|
||||
|
||||
- (NSString *) davAuthenticationType
|
||||
{
|
||||
return [[self stringForKey: @"SOGoDAVAuthenticationType"] lowercaseString];
|
||||
@@ -592,6 +749,78 @@ NSComparisonResult languageSort(id el1, id el2, void *context)
|
||||
return [self boolForKey: @"SOGoCASLogoutEnabled"];
|
||||
}
|
||||
|
||||
/* OpenId Support */
|
||||
- (NSString *) openIdConfigUrl
|
||||
{
|
||||
return [self stringForKey: @"SOGoOpenIdConfigUrl"];
|
||||
}
|
||||
|
||||
- (NSString *) openIdScope
|
||||
{
|
||||
return [self stringForKey: @"SOGoOpenIdScope"];
|
||||
}
|
||||
|
||||
- (NSString *) openIdClient
|
||||
{
|
||||
return [self stringForKey: @"SOGoOpenIdClient"];
|
||||
}
|
||||
|
||||
- (NSString *) openIdClientSecret
|
||||
{
|
||||
return [self stringForKey: @"SOGoOpenIdClientSecret"];
|
||||
}
|
||||
|
||||
- (NSString *) openIdEmailParam
|
||||
{
|
||||
NSString *emailParam;
|
||||
emailParam = [self stringForKey: @"SOGoOpenIdEmailParam"];
|
||||
if(!emailParam)
|
||||
emailParam = @"email";
|
||||
return emailParam;
|
||||
}
|
||||
|
||||
- (BOOL) openIdLogoutEnabled: (NSString *) _domain
|
||||
{
|
||||
if(_domain && [self doesLoginTypeByDomain])
|
||||
{
|
||||
NSDictionary *config;
|
||||
NSString *type;
|
||||
id value;
|
||||
if((config = [self getLoginConfigForDomain: _domain]))
|
||||
{
|
||||
if((type = [config objectForKey: @"type"]) && [type isEqualToString:@"openid"])
|
||||
return [self boolForKey: @"SOGoOpenIdLogoutEnabled" andDict: config];
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
return [self boolForKey: @"SOGoOpenIdLogoutEnabled"];
|
||||
}
|
||||
|
||||
- (BOOL) openIdSendDomainInfo
|
||||
{
|
||||
return [self boolForKey: @"SOGoOpenIdSendDomainInfo"];
|
||||
}
|
||||
|
||||
- (int) openIdTokenCheckInterval
|
||||
{
|
||||
|
||||
int v;
|
||||
|
||||
v = [self integerForKey: @"SOGoOpenIdTokenCheckInterval"];
|
||||
|
||||
if (!v)
|
||||
v = 0;
|
||||
if(v<0)
|
||||
v = 0;
|
||||
|
||||
return v;
|
||||
}
|
||||
|
||||
- (BOOL) openIdEnableRefreshToken
|
||||
{
|
||||
return [self boolForKey: @"SOGoOpenIdEnableRefreshToken"];
|
||||
}
|
||||
|
||||
/* SAML2 support */
|
||||
- (NSString *) SAML2PrivateKeyLocation
|
||||
{
|
||||
@@ -928,4 +1157,14 @@ NSComparisonResult languageSort(id el1, id el2, void *context)
|
||||
return disableExport;
|
||||
}
|
||||
|
||||
- (NSString *)urlCreateAccount
|
||||
{
|
||||
return [self stringForKey: @"SOGoURLCreateAccount"];
|
||||
}
|
||||
|
||||
- (BOOL) enableMailCleaning
|
||||
{
|
||||
return [self boolForKey: @"SOGoEnableMailCleaning"];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -91,6 +91,7 @@
|
||||
|
||||
/* properties */
|
||||
- (NSString *) domain;
|
||||
- (NSString *) loginDomain;
|
||||
- (id <SOGoSource>) authenticationSource;
|
||||
|
||||
- (NSArray *) allEmails;
|
||||
|
||||
@@ -301,6 +301,19 @@ static const NSString *kEncryptedUserNamePrefix = @"uenc";
|
||||
return [self _fetchFieldForUser: @"c_domain"];
|
||||
}
|
||||
|
||||
- (NSString *) loginDomain
|
||||
{
|
||||
NSRange r;
|
||||
NSString *domain = nil;
|
||||
r = [self->login rangeOfString: @"@"];
|
||||
if (r.location != NSNotFound)
|
||||
{
|
||||
domain = [self->login substringFromIndex: r.location+1];
|
||||
}
|
||||
return domain;
|
||||
}
|
||||
|
||||
|
||||
- (id <SOGoSource>) authenticationSource
|
||||
{
|
||||
NSString *sourceID;
|
||||
|
||||
@@ -502,6 +502,7 @@ static const NSString *kObfuscatedSecondaryEmailKey = @"obfuscatedSecondaryEmail
|
||||
perr: (SOGoPasswordPolicyError *) perr
|
||||
expire: (int *) expire
|
||||
grace: (int *) grace
|
||||
additionalInfo: (NSMutableDictionary **)_additionalInfo
|
||||
{
|
||||
NSObject <SOGoSource> *sogoSource;
|
||||
NSEnumerator *authIDs;
|
||||
@@ -520,8 +521,11 @@ static const NSString *kObfuscatedSecondaryEmailKey = @"obfuscatedSecondaryEmail
|
||||
perr: perr
|
||||
expire: expire
|
||||
grace: grace];
|
||||
if (_additionalInfo && *_additionalInfo && [sogoSource userPasswordPolicy] && [[sogoSource userPasswordPolicy] count] > 0) {
|
||||
[*_additionalInfo setObject:[sogoSource userPasswordPolicy] forKey:@"userPolicies"];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (checkOK && *domain == nil)
|
||||
{
|
||||
SOGoSystemDefaults *sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
@@ -554,6 +558,7 @@ static const NSString *kObfuscatedSecondaryEmailKey = @"obfuscatedSecondaryEmail
|
||||
perr: (SOGoPasswordPolicyError *) _perr
|
||||
expire: (int *) _expire
|
||||
grace: (int *) _grace
|
||||
additionalInfo:(NSMutableDictionary **)_additionalInfo
|
||||
{
|
||||
return [self checkLogin: _login
|
||||
password: _pwd
|
||||
@@ -561,6 +566,7 @@ static const NSString *kObfuscatedSecondaryEmailKey = @"obfuscatedSecondaryEmail
|
||||
perr: _perr
|
||||
expire: _expire
|
||||
grace: _grace
|
||||
additionalInfo: _additionalInfo
|
||||
useCache: YES];
|
||||
}
|
||||
|
||||
@@ -573,6 +579,7 @@ static const NSString *kObfuscatedSecondaryEmailKey = @"obfuscatedSecondaryEmail
|
||||
perr: (SOGoPasswordPolicyError *) _perr
|
||||
expire: (int *) _expire
|
||||
grace: (int *) _grace
|
||||
additionalInfo: (NSMutableDictionary **)_additionalInfo
|
||||
useCache: (BOOL) useCache
|
||||
{
|
||||
NSString *dictPassword, *username, *jsonUser;
|
||||
@@ -726,7 +733,8 @@ static const NSString *kObfuscatedSecondaryEmailKey = @"obfuscatedSecondaryEmail
|
||||
domain: _domain
|
||||
perr: _perr
|
||||
expire: _expire
|
||||
grace: _grace])
|
||||
grace: _grace
|
||||
additionalInfo: _additionalInfo])
|
||||
{
|
||||
checkOK = YES;
|
||||
if (!currentUser)
|
||||
@@ -1085,30 +1093,37 @@ static const NSString *kObfuscatedSecondaryEmailKey = @"obfuscatedSecondaryEmail
|
||||
domain = nil;
|
||||
infos = nil;
|
||||
|
||||
//Try to get the domain from the uid
|
||||
r = [uid rangeOfString: @"@" options: NSBackwardsSearch];
|
||||
if (r.location != NSNotFound)
|
||||
{
|
||||
// The domain is probably appended to the username;
|
||||
// make sure it is a defined domain in the configuration.
|
||||
domain = [uid substringFromIndex: (r.location + r.length)];
|
||||
if ([self isDomainDefined: domain])
|
||||
username = [uid substringToIndex: r.location];
|
||||
else
|
||||
domain = nil;
|
||||
}
|
||||
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
if ([sd enableDomainBasedUID])
|
||||
{
|
||||
r = [uid rangeOfString: @"@" options: NSBackwardsSearch];
|
||||
if (r.location != NSNotFound)
|
||||
{
|
||||
// The domain is probably appended to the username;
|
||||
// make sure it is a defined domain in the configuration.
|
||||
domain = [uid substringFromIndex: (r.location + r.length)];
|
||||
if ([self isDomainDefined: domain])
|
||||
username = [uid substringToIndex: r.location];
|
||||
else
|
||||
domain = nil;
|
||||
}
|
||||
if (domain != nil)
|
||||
infos = [self contactInfosForUserWithUIDorEmail: username
|
||||
if (domain != nil)
|
||||
{
|
||||
if ([sd enableDomainBasedUID])
|
||||
infos = [self contactInfosForUserWithUIDorEmail: username
|
||||
inDomain: domain];
|
||||
}
|
||||
else
|
||||
infos = [self contactInfosForUserWithUIDorEmail: uid
|
||||
inDomain: domain];
|
||||
}
|
||||
|
||||
if (infos == nil)
|
||||
{
|
||||
// If the user was not found using the domain or if no domain was detected,
|
||||
// search using the original uid.
|
||||
infos = [self contactInfosForUserWithUIDorEmail: uid
|
||||
inDomain: nil];
|
||||
}
|
||||
|
||||
return infos;
|
||||
}
|
||||
@@ -1137,8 +1152,7 @@ static const NSString *kObfuscatedSecondaryEmailKey = @"obfuscatedSecondaryEmail
|
||||
|
||||
if ([currentUser isKindOfClass: NSNullK])
|
||||
currentUser = nil;
|
||||
else if (!([currentUser objectForKey: @"emails"]
|
||||
&& [currentUser objectForKey: @"cn"]))
|
||||
else if (!([currentUser objectForKey: @"emails"] && [currentUser objectForKey: @"cn"]))
|
||||
{
|
||||
// We make sure that we either have no occurence of a cache entry or
|
||||
// that we have an occurence with only a cached password. In the
|
||||
@@ -1155,6 +1169,19 @@ static const NSString *kObfuscatedSecondaryEmailKey = @"obfuscatedSecondaryEmail
|
||||
}
|
||||
else
|
||||
newUser = NO;
|
||||
|
||||
if(!domain)
|
||||
{
|
||||
//No domain provided is there one?
|
||||
NSRange r;
|
||||
r = [uid rangeOfString: @"@" options: NSBackwardsSearch];
|
||||
if (r.location != NSNotFound)
|
||||
{
|
||||
domain = [uid substringFromIndex: (r.location + r.length)];
|
||||
if (![self isDomainDefined: domain])
|
||||
domain = nil;
|
||||
}
|
||||
}
|
||||
[self _fillContactInfosForUser: currentUser
|
||||
withUIDorEmail: aUID
|
||||
inDomain: domain];
|
||||
@@ -1332,8 +1359,7 @@ static const NSString *kObfuscatedSecondaryEmailKey = @"obfuscatedSecondaryEmail
|
||||
- (NSArray *) fetchContactsMatching: (NSString *) filter
|
||||
inDomain: (NSString *) domain
|
||||
{
|
||||
return [self
|
||||
_fetchEntriesInSources: [self addressBookSourceIDsInDomain: domain]
|
||||
return [self _fetchEntriesInSources: [self addressBookSourceIDsInDomain: domain]
|
||||
matching: filter
|
||||
inDomain: domain];
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
#import "SOGoConstants.h"
|
||||
|
||||
@class NSString;
|
||||
@class NSMutableDictionary;
|
||||
|
||||
@class WOContext;
|
||||
@class WOCookie;
|
||||
@@ -37,25 +38,29 @@
|
||||
|
||||
+ (id) sharedSOGoWebAuthenticator;
|
||||
|
||||
- (BOOL) checkLogin: (NSString *) _login
|
||||
password: (NSString *) _pwd
|
||||
domain: (NSString **) _domain
|
||||
perr: (SOGoPasswordPolicyError *) _perr
|
||||
expire: (int *) _expire
|
||||
grace: (int *) _grace;
|
||||
- (BOOL)checkLogin:(NSString *)_login
|
||||
password:(NSString *)_pwd
|
||||
domain:(NSString **)_domain
|
||||
perr:(SOGoPasswordPolicyError *)_perr
|
||||
expire:(int *)_expire
|
||||
grace:(int *)_grace
|
||||
additionalInfo:(NSMutableDictionary **)_additionalInfo;
|
||||
|
||||
- (BOOL) checkLogin: (NSString *) _login
|
||||
password: (NSString *) _pwd
|
||||
domain: (NSString **) _domain
|
||||
perr: (SOGoPasswordPolicyError *) _perr
|
||||
expire: (int *) _expire
|
||||
grace: (int *) _grace
|
||||
useCache: (BOOL) useCache;
|
||||
- (BOOL)checkLogin:(NSString *)_login
|
||||
password:(NSString *)_pwd
|
||||
domain:(NSString **)_domain
|
||||
perr:(SOGoPasswordPolicyError *)_perr
|
||||
expire:(int *)_expire
|
||||
grace:(int *)_grace
|
||||
additionalInfo:(NSMutableDictionary **)_additionalInfo
|
||||
useCache:(BOOL)useCache;
|
||||
|
||||
- (WOCookie *) cookieWithUsername: (NSString *) username
|
||||
andPassword: (NSString *) password
|
||||
inContext: (WOContext *) context;
|
||||
|
||||
- (NSArray *)getCookiesIfNeeded: (WOContext *)_ctx;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* _SOGOWEBAUTHENTICATOR_H_ */
|
||||
@@ -35,6 +35,7 @@
|
||||
|
||||
#import "SOGoCache.h"
|
||||
#import "SOGoCASSession.h"
|
||||
#import "SOGoOpenIdSession.h"
|
||||
#import "SOGoPermissions.h"
|
||||
#import "SOGoSession.h"
|
||||
#import "SOGoSystemDefaults.h"
|
||||
@@ -102,7 +103,8 @@
|
||||
domain: &domain
|
||||
perr: &perr
|
||||
expire: &expire
|
||||
grace: &grace];
|
||||
grace: &grace
|
||||
additionalInfo: nil];
|
||||
}
|
||||
|
||||
- (BOOL) checkLogin: (NSString *) _login
|
||||
@@ -111,6 +113,7 @@
|
||||
perr: (SOGoPasswordPolicyError *) _perr
|
||||
expire: (int *) _expire
|
||||
grace: (int *) _grace
|
||||
additionalInfo: (NSMutableDictionary **)_additionalInfo
|
||||
{
|
||||
return [self checkLogin: _login
|
||||
password: _pwd
|
||||
@@ -118,6 +121,7 @@
|
||||
perr: _perr
|
||||
expire: _expire
|
||||
grace: _grace
|
||||
additionalInfo: _additionalInfo
|
||||
useCache: YES];
|
||||
}
|
||||
|
||||
@@ -127,24 +131,56 @@
|
||||
perr: (SOGoPasswordPolicyError *) _perr
|
||||
expire: (int *) _expire
|
||||
grace: (int *) _grace
|
||||
additionalInfo: (NSMutableDictionary **)_additionalInfo
|
||||
useCache: (BOOL) _useCache
|
||||
{
|
||||
SOGoCASSession *session;
|
||||
SOGoCASSession *casSession;
|
||||
SOGoOpenIdSession * openIdSession;
|
||||
SOGoSystemDefaults *sd;
|
||||
NSString *authenticationType;
|
||||
NSString* loginDomain;
|
||||
BOOL rc;
|
||||
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
|
||||
//Basic check
|
||||
if(!_login)
|
||||
return NO;
|
||||
if(_login && [_login length] == 0)
|
||||
return NO;
|
||||
|
||||
loginDomain = nil;
|
||||
if(*_domain == nil || [*_domain length] == 0)
|
||||
{
|
||||
NSRange r;
|
||||
r = [_login rangeOfString: @"@"];
|
||||
if (r.location != NSNotFound)
|
||||
{
|
||||
loginDomain = [_login substringFromIndex: r.location+1];
|
||||
}
|
||||
}
|
||||
|
||||
if([sd doesLoginTypeByDomain])
|
||||
authenticationType = [sd getLoginTypeForDomain: loginDomain];
|
||||
else
|
||||
authenticationType = [sd authenticationType];
|
||||
|
||||
authenticationType = [sd authenticationType];
|
||||
if ([authenticationType isEqualToString: @"cas"])
|
||||
{
|
||||
session = [SOGoCASSession CASSessionWithIdentifier: _pwd fromProxy: NO];
|
||||
if (session)
|
||||
rc = [[session login] isEqualToString: _login];
|
||||
casSession = [SOGoCASSession CASSessionWithIdentifier: _pwd fromProxy: NO];
|
||||
if (casSession)
|
||||
rc = [[casSession login] isEqualToString: _login];
|
||||
else
|
||||
rc = NO;
|
||||
}
|
||||
else if ([authenticationType isEqualToString: @"openid"])
|
||||
{
|
||||
openIdSession = [SOGoOpenIdSession OpenIdSessionWithToken: _pwd domain: loginDomain];
|
||||
if (openIdSession)
|
||||
rc = [[openIdSession login: _login] isEqualToString: _login];
|
||||
else
|
||||
rc = NO;
|
||||
}
|
||||
#if defined(SAML2_CONFIG)
|
||||
else if ([authenticationType isEqualToString: @"saml2"])
|
||||
{
|
||||
@@ -164,8 +200,8 @@
|
||||
perr: _perr
|
||||
expire: _expire
|
||||
grace: _grace
|
||||
additionalInfo: _additionalInfo
|
||||
useCache: _useCache];
|
||||
|
||||
//[self logWithFormat: @"Checked login with ppolicy enabled: %d %d %d", *_perr, *_expire, *_grace];
|
||||
|
||||
// It's important to return the real value here. The callee will handle
|
||||
@@ -244,13 +280,15 @@
|
||||
login: &login
|
||||
domain: &domain
|
||||
password: &pwd];
|
||||
|
||||
|
||||
|
||||
if (![self checkLogin: login
|
||||
password: pwd
|
||||
domain: &domain
|
||||
perr: &perr
|
||||
expire: &expire
|
||||
grace: &grace])
|
||||
grace: &grace
|
||||
additionalInfo: nil])
|
||||
return nil;
|
||||
|
||||
if (domain && [login rangeOfString: @"@"].location == NSNotFound)
|
||||
@@ -266,32 +304,42 @@
|
||||
{
|
||||
NSString *authType, *password;
|
||||
SOGoSystemDefaults *sd;
|
||||
SOGoUser *user;
|
||||
NSRange r;
|
||||
NSString *loginDomain, *login;
|
||||
|
||||
password = [self passwordInContext: context];
|
||||
if ([password length])
|
||||
{
|
||||
user = [self userInContext: context];
|
||||
login = [user loginInDomain];
|
||||
r = [login rangeOfString: @"@"];
|
||||
if (r.location != NSNotFound)
|
||||
loginDomain = [login substringFromIndex: r.location+1];
|
||||
else
|
||||
loginDomain = nil;
|
||||
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
authType = [sd authenticationType];
|
||||
if([sd doesLoginTypeByDomain])
|
||||
authType = [sd getLoginTypeForDomain: loginDomain];
|
||||
else
|
||||
authType = [sd authenticationType];
|
||||
|
||||
if ([authType isEqualToString: @"cas"])
|
||||
{
|
||||
SOGoCASSession *session;
|
||||
SOGoUser *user;
|
||||
NSString *service, *scheme;
|
||||
|
||||
session = [SOGoCASSession CASSessionWithIdentifier: password
|
||||
fromProxy: NO];
|
||||
|
||||
user = [self userInContext: context];
|
||||
// Try configured CAS service name first
|
||||
service = [[user domainDefaults] imapCASServiceName];
|
||||
if (!service)
|
||||
{
|
||||
// We must NOT assume the scheme exists
|
||||
scheme = [server scheme];
|
||||
|
||||
if (!scheme)
|
||||
scheme = @"imap";
|
||||
|
||||
service = [NSString stringWithFormat: @"%@://%@",
|
||||
scheme, [server host]];
|
||||
}
|
||||
@@ -300,10 +348,18 @@
|
||||
[session invalidateTicketForService: service];
|
||||
|
||||
password = [session ticketForService: service];
|
||||
|
||||
if ([password length] || renew)
|
||||
[session updateCache];
|
||||
}
|
||||
else if ([authType isEqualToString: @"openid"])
|
||||
{
|
||||
SOGoOpenIdSession* session;
|
||||
|
||||
//If the token has been refresh during the request, we need to use the new access_token
|
||||
//as the one from the cookie is no more valid
|
||||
session = [SOGoOpenIdSession OpenIdSessionWithToken: password domain: loginDomain];
|
||||
password = [session getCurrentToken];
|
||||
}
|
||||
#if defined(SAML2_CONFIG)
|
||||
else if ([authType isEqualToString: @"saml2"])
|
||||
{
|
||||
@@ -326,6 +382,16 @@
|
||||
return password;
|
||||
}
|
||||
|
||||
- (NSString *) smtpPasswordInContext: (WOContext *) context
|
||||
forURL: (NSURL *) server
|
||||
{
|
||||
NSString *password;
|
||||
|
||||
password = [self imapPasswordInContext: context forURL: server forceRenew:NO];
|
||||
|
||||
return password;
|
||||
}
|
||||
|
||||
/* create SOGoUser */
|
||||
|
||||
- (SOGoUser *) userWithLogin: (NSString *) login
|
||||
@@ -430,4 +496,57 @@
|
||||
return authCookie;
|
||||
}
|
||||
|
||||
- (NSArray *) getCookiesIfNeeded: (WOContext *)_ctx
|
||||
{
|
||||
NSArray *listCookies = nil;
|
||||
SOGoSystemDefaults *sd;
|
||||
NSString *authType, *username, *login, *loginDomain;
|
||||
NSRange r;
|
||||
SOGoUser *user;
|
||||
|
||||
user = [self userInContext: _ctx];
|
||||
login = [user loginDomain];
|
||||
r = [login rangeOfString: @"@"];
|
||||
if (r.location != NSNotFound)
|
||||
loginDomain = [login substringFromIndex: r.location+1];
|
||||
else
|
||||
loginDomain = nil;
|
||||
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
if(loginDomain && [sd doesLoginTypeByDomain])
|
||||
authType = [sd getLoginTypeForDomain: loginDomain];
|
||||
else
|
||||
authType = [sd authenticationType];
|
||||
if([authType isEqualToString:@"openid"] && [sd openIdEnableRefreshToken])
|
||||
{
|
||||
NSString *currentPassword, *newPassword;
|
||||
SOGoOpenIdSession *openIdSession;
|
||||
WOCookie* newCookie;
|
||||
|
||||
|
||||
currentPassword = [self passwordInContext: _ctx];
|
||||
newPassword = [self imapPasswordInContext: _ctx forURL: nil forceRenew: NO];
|
||||
if(currentPassword && newPassword && ![newPassword isEqualToString: currentPassword])
|
||||
{
|
||||
|
||||
openIdSession = [SOGoOpenIdSession OpenIdSessionWithToken: newPassword domain: loginDomain];
|
||||
if (openIdSession)
|
||||
username = [openIdSession login: @""]; //Force to refresh the name
|
||||
else
|
||||
username = [[self userInContext: _ctx] login];
|
||||
newCookie = [self cookieWithUsername: username
|
||||
andPassword: newPassword
|
||||
inContext: _ctx];
|
||||
listCookies = [[NSArray alloc] initWithObjects: newCookie, nil];
|
||||
[listCookies autorelease];
|
||||
}
|
||||
if(listCookies && [listCookies isKindOfClass:[NSArray class]] && [listCookies count] > 0)
|
||||
return listCookies;
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
else
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end /* SOGoWebAuthenticator */
|
||||
+77
-25
@@ -245,6 +245,20 @@
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL) checkLogin: (NSString *) _login
|
||||
password: (NSString *) _pwd
|
||||
perr: (SOGoPasswordPolicyError *) _perr
|
||||
expire: (int *) _expire
|
||||
grace: (int *) _grace
|
||||
{
|
||||
return [self checkLogin: _login
|
||||
password: _pwd
|
||||
perr: _perr
|
||||
expire: _perr
|
||||
grace: _grace
|
||||
disablepasswordPolicyCheck: NO];
|
||||
}
|
||||
|
||||
//
|
||||
// SQL sources don't support right now all the password policy
|
||||
// stuff supported by OpenLDAP (and others). If we want to support
|
||||
@@ -256,6 +270,7 @@
|
||||
perr: (SOGoPasswordPolicyError *) _perr
|
||||
expire: (int *) _expire
|
||||
grace: (int *) _grace
|
||||
disablepasswordPolicyCheck: (BOOL) _disablepasswordPolicyCheck
|
||||
{
|
||||
EOAdaptorChannel *channel;
|
||||
EOQualifier *qualifier;
|
||||
@@ -335,10 +350,69 @@
|
||||
else
|
||||
[self errorWithFormat:@"failed to acquire channel for URL: %@",
|
||||
[_viewURL absoluteString]];
|
||||
|
||||
if (YES == rc && !_disablepasswordPolicyCheck) {
|
||||
[self checkPasswordPolicyWithPassword:_pwd perr: _perr];
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a given password against the configured user password policies.
|
||||
*
|
||||
* This method checks if the provided password complies with all defined
|
||||
* password policies for the user. Each policy is expected to include
|
||||
* a regular expression (`regex`) that the password must match.
|
||||
*
|
||||
* If the password violates any policy, the method sets an appropriate error
|
||||
* in the `perr` parameter and stops further validation.
|
||||
*
|
||||
* @param password the password to validate.
|
||||
* @param perr will be set to indicate a policy violation, if the password
|
||||
* does not meet the required standards. Possible values include:
|
||||
* `PolicyInsufficientPasswordQuality` for insufficient complexity.
|
||||
* @return YES if the password satisfies all policies, NO otherwise.
|
||||
*
|
||||
* @note This method assumes that `_userPasswordPolicy` is an array of dictionaries,
|
||||
* where each dictionary represents a password policy with at least a "regex" key.
|
||||
* @warning If a policy does not include a "regex" key, an error will be logged, and
|
||||
* the method will continue to the next policy.
|
||||
*/
|
||||
- (BOOL) checkPasswordPolicyWithPassword: (NSString *)password perr: (SOGoPasswordPolicyError *)perr
|
||||
{
|
||||
BOOL isPolicyOk;
|
||||
NSDictionary *policy;
|
||||
NSEnumerator *policies;
|
||||
NSRange match;
|
||||
NSString *regex;
|
||||
|
||||
isPolicyOk = YES;
|
||||
|
||||
if ([_userPasswordPolicy count])
|
||||
{
|
||||
policies = [_userPasswordPolicy objectEnumerator];
|
||||
while (isPolicyOk && (policy = [policies nextObject]))
|
||||
{
|
||||
regex = [policy objectForKey: @"regex"];
|
||||
if (regex)
|
||||
{
|
||||
match = [password rangeOfString: regex options: NSRegularExpressionSearch];
|
||||
isPolicyOk = isPolicyOk && match.length > 0;
|
||||
if (match.length == 0)
|
||||
{
|
||||
// [self errorWithFormat: @"Password not conform to policy %@ (%@)", regex, [policy objectForKey: @"label"]];
|
||||
*perr = PolicyInsufficientPasswordQuality;
|
||||
}
|
||||
}
|
||||
else
|
||||
[self errorWithFormat: @"Invalid password policy (missing regex): %@", policy];
|
||||
}
|
||||
}
|
||||
|
||||
return isPolicyOk;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change a user's password.
|
||||
* @param login the user's login name.
|
||||
@@ -357,11 +431,8 @@
|
||||
BOOL didChange, isOldPwdOk, isPolicyOk;
|
||||
EOAdaptorChannel *channel;
|
||||
GCSChannelManager *cm;
|
||||
NSDictionary *policy;
|
||||
NSEnumerator *policies;
|
||||
NSException *ex;
|
||||
NSRange match;
|
||||
NSString *sqlstr, *regex;
|
||||
NSString *sqlstr;
|
||||
|
||||
*perr = -1;
|
||||
isOldPwdOk = NO;
|
||||
@@ -369,30 +440,11 @@
|
||||
didChange = NO;
|
||||
|
||||
// Verify current password
|
||||
isOldPwdOk = [self checkLogin:login password:oldPassword perr:perr expire:0 grace:0];
|
||||
isOldPwdOk = [self checkLogin:login password:oldPassword perr:perr expire:0 grace:0 disablepasswordPolicyCheck: YES];
|
||||
|
||||
if (isOldPwdOk || passwordRecovery)
|
||||
{
|
||||
if ([_userPasswordPolicy count])
|
||||
{
|
||||
policies = [_userPasswordPolicy objectEnumerator];
|
||||
while (isPolicyOk && (policy = [policies nextObject]))
|
||||
{
|
||||
regex = [policy objectForKey: @"regex"];
|
||||
if (regex)
|
||||
{
|
||||
match = [newPassword rangeOfString: regex options: NSRegularExpressionSearch];
|
||||
isPolicyOk = isPolicyOk && match.length > 0;
|
||||
if (match.length == 0)
|
||||
{
|
||||
// [self errorWithFormat: @"Password not conform to policy %@ (%@)", regex, [policy objectForKey: @"label"]];
|
||||
*perr = PolicyInsufficientPasswordQuality;
|
||||
}
|
||||
}
|
||||
else
|
||||
[self errorWithFormat: @"Invalid password policy (missing regex): %@", policy];
|
||||
}
|
||||
}
|
||||
isPolicyOk = [self checkPasswordPolicyWithPassword:newPassword perr: perr];
|
||||
}
|
||||
|
||||
if ((isOldPwdOk || passwordRecovery) && isPolicyOk)
|
||||
|
||||
@@ -267,4 +267,8 @@
|
||||
return ret;
|
||||
}
|
||||
|
||||
- (NSString *) applicationName {
|
||||
return @"SOGo";
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -21,6 +21,7 @@ $(SOGO_TOOL)_OBJC_FILES += \
|
||||
SOGoToolRemoveDoubles.m \
|
||||
SOGoToolRenameUser.m \
|
||||
SOGoToolCheckupUser.m \
|
||||
SOGoToolCleanOpenIdSessions.m \
|
||||
SOGoToolCleanupUser.m \
|
||||
SOGoToolRestore.m \
|
||||
SOGoToolCreateFolder.m \
|
||||
|
||||
@@ -0,0 +1,137 @@
|
||||
/* SOGoToolCleanOpenIdSessions.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2012-2021 Inverse inc.
|
||||
*
|
||||
* 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 <Foundation/NSArray.h>
|
||||
#import <Foundation/NSCalendarDate.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSException.h>
|
||||
#import <Foundation/NSUserDefaults.h>
|
||||
|
||||
#import <GDLAccess/EOAdaptorChannel.h>
|
||||
|
||||
#import <GDLContentStore/GCSChannelManager.h>
|
||||
#import <GDLContentStore/NSURL+GCS.h>
|
||||
|
||||
#import <NGExtensions/NSNull+misc.h>
|
||||
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
#import <SOGo/SOGoSession.h>
|
||||
#import <SOGo/SOGoOpenIdSession.h>
|
||||
|
||||
#import "SOGoTool.h"
|
||||
|
||||
@interface SOGoToolCleanOpenIdSessions : SOGoTool
|
||||
@end
|
||||
|
||||
@implementation SOGoToolCleanOpenIdSessions
|
||||
|
||||
+ (NSString *) command
|
||||
{
|
||||
return @"clean-openid-sessions";
|
||||
}
|
||||
|
||||
+ (NSString *) description
|
||||
{
|
||||
return @"clean user openid sessions that are expired";
|
||||
}
|
||||
|
||||
- (void) usage
|
||||
{
|
||||
fprintf (stderr, "clean-openid-sessions\n\n"
|
||||
"\n"
|
||||
"The clean-openid-sessions action should be configured as a cronjob.\n");
|
||||
}
|
||||
|
||||
- (BOOL) cleanExpiredOpenIdSession
|
||||
{
|
||||
BOOL rc;
|
||||
EOAdaptorChannel *channel;
|
||||
GCSChannelManager *cm;
|
||||
NSArray *attrs;
|
||||
NSDictionary *qresult;
|
||||
NSException *ex;
|
||||
NSString *sql, *sessionsFolderURL, *sessionID;
|
||||
NSURL *tableURL;
|
||||
NSUserDefaults *ud;
|
||||
|
||||
unsigned int now;
|
||||
|
||||
rc = YES;
|
||||
ud = [NSUserDefaults standardUserDefaults];
|
||||
now = [[NSCalendarDate calendarDate] timeIntervalSince1970];
|
||||
sessionID = nil;
|
||||
|
||||
sessionsFolderURL = [ud stringForKey: @"OCSOpenIdURL"];
|
||||
if (!sessionsFolderURL)
|
||||
{
|
||||
if (verbose)
|
||||
NSLog(@"Couldn't read OCSOpenIdURL");
|
||||
return rc = NO;
|
||||
}
|
||||
|
||||
tableURL = [[NSURL alloc] initWithString: sessionsFolderURL];
|
||||
cm = [GCSChannelManager defaultChannelManager];
|
||||
channel = [cm acquireOpenChannelForURL: tableURL];
|
||||
if (!channel)
|
||||
{
|
||||
/* FIXME: nice error msg */
|
||||
NSLog(@"Can't acquire channel");
|
||||
return rc = NO;
|
||||
}
|
||||
|
||||
sql = [NSString stringWithFormat: @"SELECT c_user_session FROM %@ WHERE c_access_token_expires_in <= %d AND c_refresh_token_expires_in <= %d",
|
||||
[tableURL gcsTableName], now, now];
|
||||
ex = [channel evaluateExpressionX: sql];
|
||||
if (ex)
|
||||
{
|
||||
NSLog(@"%@", [ex reason]);
|
||||
[ex raise];
|
||||
return rc = NO;
|
||||
}
|
||||
|
||||
attrs = [channel describeResults: NO];
|
||||
while ((qresult = [channel fetchAttributes: attrs withZone: NULL]))
|
||||
{
|
||||
sessionID = [qresult objectForKey: @"c_user_session"];
|
||||
if (sessionID)
|
||||
{
|
||||
if (verbose)
|
||||
NSLog(@"Removing session %@", sessionID);
|
||||
[SOGoOpenIdSession deleteValueForSessionKey: sessionID];
|
||||
}
|
||||
}
|
||||
[cm releaseChannel: channel immediately: YES];
|
||||
|
||||
if (verbose && sessionID == nil)
|
||||
NSLog(@"No session to remove on openId table");
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
- (BOOL) run
|
||||
{
|
||||
BOOL rc;
|
||||
|
||||
rc = [self cleanExpiredOpenIdSession];
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -33,6 +33,7 @@
|
||||
|
||||
#import <SOGo/NSString+Utilities.h>
|
||||
#import <SOGo/SOGoSession.h>
|
||||
#import <SOGo/SOGoOpenIdSession.h>
|
||||
|
||||
#import "SOGoTool.h"
|
||||
|
||||
|
||||
@@ -68,6 +68,7 @@ typedef enum
|
||||
" -p credentialFile Specify the file containing the sieve admin credentials\n"
|
||||
" The file should contain a single line:\n"
|
||||
" username:password\n"
|
||||
" -F force the activation of the sieve script in case external scripts. Must be the las targument after -p credentialsFile"
|
||||
" Examples:\n"
|
||||
" sogo-tool user-preferences get defaults janedoe SOGoLanguage\n"
|
||||
" sogo-tool user-preferences unset settings janedoe Mail\n"
|
||||
@@ -144,9 +145,19 @@ typedef enum
|
||||
account = [folder lookupName: @"0" inContext: localContext acquire: NO];
|
||||
[account setContext: localContext];
|
||||
|
||||
error = [account updateFiltersWithUsername: authname
|
||||
andPassword: authpwd
|
||||
forceActivation: NO];
|
||||
if([[arguments lastObject] isEqualToString: @"-F"])
|
||||
{
|
||||
error = [account updateFiltersWithUsername: authname
|
||||
andPassword: authpwd
|
||||
forceActivation: YES];
|
||||
}
|
||||
else
|
||||
{
|
||||
error = [account updateFiltersWithUsername: authname
|
||||
andPassword: authpwd
|
||||
forceActivation: NO];
|
||||
}
|
||||
|
||||
if (error)
|
||||
return NO;
|
||||
}
|
||||
@@ -248,7 +259,7 @@ typedef enum
|
||||
break;
|
||||
|
||||
case UserPreferencesSet:
|
||||
if (max > 4)
|
||||
if (![arguments containsObject: @"-f"])
|
||||
{
|
||||
/* value specified on command line */
|
||||
value = [sanitizedArguments objectAtIndex: 4];
|
||||
|
||||
+1
-2
@@ -114,8 +114,7 @@
|
||||
if (count < max)
|
||||
{
|
||||
max -= count;
|
||||
toolArguments = [arguments
|
||||
subarrayWithRange: NSMakeRange (count, max)];
|
||||
toolArguments = [arguments subarrayWithRange: NSMakeRange (count, max)];
|
||||
[toolArguments retain];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,3 +233,10 @@
|
||||
"passwordRecovery_SecretQuestion2" = "What was your first car ?";
|
||||
"passwordRecovery_SecretQuestion3" = "What is your favorite movie ?";
|
||||
"passwordRecovery_SecondaryEmail" = "Secondary E-mail";
|
||||
|
||||
/* Password */
|
||||
"POLICY_MIN_LOWERCASE_LETTER" = "Minimum of %{0} lowercase letter";
|
||||
"POLICY_MIN_UPPERCASE_LETTER" = "Minimum of %{0} uppercase letter";
|
||||
"POLICY_MIN_DIGIT" = "Minimum of %{0} digit";
|
||||
"POLICY_MIN_SPECIAL_SYMBOLS" = "Minimum of %{0} special symbols";
|
||||
"POLICY_MIN_LENGTH" = "Minimum length of %{0} characters";
|
||||
@@ -233,3 +233,10 @@
|
||||
"passwordRecovery_SecretQuestion2" = "Quelle était votre première voiture ?";
|
||||
"passwordRecovery_SecretQuestion3" = "Quel est votre film préféré ?";
|
||||
"passwordRecovery_SecondaryEmail" = "E-mail secondaire";
|
||||
|
||||
/* Password */
|
||||
"POLICY_MIN_LOWERCASE_LETTER" = "Au moins %{0} lettre(s) minuscule(s)";
|
||||
"POLICY_MIN_UPPERCASE_LETTER" = "Au moins %{0} lettre(s) majuscule(s)";
|
||||
"POLICY_MIN_DIGIT" = "Au moins %{0} chiffre(s)";
|
||||
"POLICY_MIN_SPECIAL_SYMBOLS" = "Au moins %{0} caractère(s) special(aux)";
|
||||
"POLICY_MIN_LENGTH" = "Longueur d'au moins %{0} caractère(s)";
|
||||
@@ -488,13 +488,14 @@
|
||||
&& [user isSuperUser]);
|
||||
}
|
||||
|
||||
- (BOOL) usesCASAuthentication
|
||||
|
||||
- (BOOL) usesOpenIdAuthentication
|
||||
{
|
||||
SOGoSystemDefaults *sd;
|
||||
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
|
||||
return [[sd authenticationType] isEqualToString: @"cas"];
|
||||
return [[sd authenticationType] isEqualToString: @"openid"];
|
||||
}
|
||||
|
||||
- (BOOL) usesSAML2Authenticationx
|
||||
@@ -537,19 +538,34 @@
|
||||
BOOL canLogoff;
|
||||
id auth;
|
||||
SOGoSystemDefaults *sd;
|
||||
NSString *authType;
|
||||
NSString *authType, *login, *loginDomain;
|
||||
NSRange r;
|
||||
|
||||
auth = [[self clientObject] authenticatorInContext: context];
|
||||
if ([auth respondsToSelector: @selector (cookieNameInContext:)])
|
||||
{
|
||||
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
authType = [sd authenticationType];
|
||||
if ([authType isEqualToString: @"cas"])
|
||||
canLogoff = [sd CASLogoutEnabled];
|
||||
else if ([authType isEqualToString: @"saml2"])
|
||||
canLogoff = [sd SAML2LogoutEnabled];
|
||||
|
||||
login = [[context activeUser] login];
|
||||
r = [login rangeOfString: @"@"];
|
||||
if (r.location != NSNotFound)
|
||||
loginDomain = [login substringFromIndex: r.location+1];
|
||||
else
|
||||
canLogoff = [[auth cookieNameInContext: context] length] > 0;
|
||||
loginDomain = nil;
|
||||
if(loginDomain && [sd doesLoginTypeByDomain])
|
||||
authType = [sd getLoginTypeForDomain: loginDomain];
|
||||
else
|
||||
authType = [sd authenticationType];
|
||||
|
||||
if ([authType isEqualToString: @"cas"])
|
||||
canLogoff = [sd CASLogoutEnabled];
|
||||
else if ([authType isEqualToString: @"saml2"])
|
||||
canLogoff = [sd SAML2LogoutEnabled];
|
||||
else if ([authType isEqualToString: @"openid"])
|
||||
canLogoff = [sd openIdLogoutEnabled: loginDomain];
|
||||
else
|
||||
canLogoff = [[auth cookieNameInContext: context] length] > 0;
|
||||
}
|
||||
else
|
||||
canLogoff = NO;
|
||||
|
||||
@@ -85,6 +85,7 @@ _xmlCharsetForCharset (NSString *charset)
|
||||
{ @"windows-1255", XML_CHAR_ENCODING_ERROR}, // unsupported, will trigger windows-1255 -> utf8 conversion
|
||||
{ @"windows-1256", XML_CHAR_ENCODING_ERROR}, // unsupported, will trigger windows-1255 -> utf8 conversion
|
||||
{ @"windows-1257", XML_CHAR_ENCODING_ERROR}, // unsupported, will trigger windows-1257 -> utf8 conversion
|
||||
{ @"windows-874", XML_CHAR_ENCODING_ERROR}, // unsupported, will trigger windows-874 -> utf8 conversion
|
||||
{ @"gb2312", XML_CHAR_ENCODING_ERROR}, // unsupported, will trigger gb2312 -> utf8 conversion
|
||||
{ @"gbk", XML_CHAR_ENCODING_ERROR}, // unsupported, will trigger gb2312 -> utf8 conversion
|
||||
{ @"gb18030", XML_CHAR_ENCODING_ERROR}, // unsupported, will trigger gb2312 -> utf8 conversion
|
||||
@@ -521,7 +522,25 @@ static NSString *_sanitizeHtmlForDisplay(NSString *content)
|
||||
{
|
||||
/* [resultPart appendString:
|
||||
@"src=\"/SOGo.woa/WebServerResources/empty.gif\""]; */
|
||||
name = @"unsafe-src";
|
||||
|
||||
//Check if the image is base64 ant not url
|
||||
NSUInteger i, c;
|
||||
BOOL isBase64 = NO;
|
||||
for (i = 0, c = [_attributes count]; i < c; i++) {
|
||||
NSString *type, *value;
|
||||
type = [_attributes nameAtIndex:i];
|
||||
if ([type isEqualToString:@"src"])
|
||||
{
|
||||
value = [_attributes valueAtIndex:i];
|
||||
if([value length] > 4 && [[value substringToIndex: 4] isEqualToString:@"data"])
|
||||
{
|
||||
isBase64 = YES;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(!isBase64)
|
||||
name = @"unsafe-src";
|
||||
}
|
||||
else
|
||||
skipAttribute = YES;
|
||||
|
||||
@@ -170,60 +170,95 @@
|
||||
|
||||
emailEvent = [self _emailEvent];
|
||||
if (emailEvent)
|
||||
{
|
||||
*eventObject = [self _eventObjectWithUID: [emailEvent uid]];
|
||||
if ([*eventObject isNew])
|
||||
{
|
||||
*eventObject = [self _eventObjectWithUID: [emailEvent uid]];
|
||||
if ([*eventObject isNew])
|
||||
{
|
||||
chosenEvent = emailEvent;
|
||||
[*eventObject saveCalendar: [emailEvent parent]];
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([emailEvent recurrenceId])
|
||||
{
|
||||
// Event attached to email is not complete -- retrieve it
|
||||
// from the database.
|
||||
NSString *recurrenceTime;
|
||||
|
||||
recurrenceTime = [NSString stringWithFormat: @"%f",
|
||||
[[emailEvent recurrenceId] timeIntervalSince1970]];
|
||||
calendarEvent = (iCalEvent *)[*eventObject lookupOccurrence: recurrenceTime];
|
||||
}
|
||||
else
|
||||
calendarEvent = (iCalEvent *) [*eventObject component: NO secure: NO];
|
||||
|
||||
if (calendarEvent != nil)
|
||||
{
|
||||
// Calendar event still exists -- verify which of the calendar
|
||||
// and email events is the most recent. We must also update
|
||||
// the event (or recurrence-id) with the email's content, otherwise
|
||||
// we would never get major properties updates
|
||||
if ([calendarEvent compare: emailEvent] == NSOrderedAscending)
|
||||
{
|
||||
iCalCalendar *parent;
|
||||
|
||||
parent = [calendarEvent parent];
|
||||
[parent removeChild: calendarEvent];
|
||||
[parent addChild: emailEvent];
|
||||
[*eventObject saveCalendar: parent];
|
||||
[*eventObject flush];
|
||||
chosenEvent = emailEvent;
|
||||
}
|
||||
else
|
||||
{
|
||||
chosenEvent = calendarEvent;
|
||||
if (![[[chosenEvent parent] method] length])
|
||||
[[chosenEvent parent] setMethod: [[emailEvent parent] method]];
|
||||
}
|
||||
}
|
||||
else
|
||||
chosenEvent = emailEvent;
|
||||
}
|
||||
|
||||
organizer = [chosenEvent organizer];
|
||||
if (![[organizer rfc822Email] length])
|
||||
[self _fixOrganizerInEvent: chosenEvent];
|
||||
chosenEvent = emailEvent;
|
||||
[*eventObject saveCalendar: [emailEvent parent]];
|
||||
}
|
||||
else
|
||||
{
|
||||
if ([emailEvent recurrenceId])
|
||||
{
|
||||
// Event attached to email is not complete -- retrieve it
|
||||
// from the database.
|
||||
NSString *recurrenceTime;
|
||||
|
||||
recurrenceTime = [NSString stringWithFormat: @"%f",
|
||||
[[emailEvent recurrenceId] timeIntervalSince1970]];
|
||||
calendarEvent = (iCalEvent *)[*eventObject lookupOccurrence: recurrenceTime];
|
||||
}
|
||||
else
|
||||
calendarEvent = (iCalEvent *) [*eventObject component: NO secure: NO];
|
||||
|
||||
if (calendarEvent != nil)
|
||||
{
|
||||
// Calendar event still exists -- verify which of the calendar
|
||||
// and email events is the most recent. We must also update
|
||||
// the event (or recurrence-id) with the email's content, otherwise
|
||||
// we would never get major properties updates
|
||||
if ([calendarEvent compare: emailEvent] == NSOrderedAscending)
|
||||
{
|
||||
iCalCalendar *parent;
|
||||
parent = [calendarEvent parent];
|
||||
|
||||
//careful, sogo vcalendar objets for reccurent event needs to have the main event first, then all the vevent with reccurence-id
|
||||
if(![emailEvent hasRecurrenceRules] || [emailEvent recurrenceId])
|
||||
{
|
||||
[parent removeChild: calendarEvent];
|
||||
[parent addChild: emailEvent];
|
||||
}
|
||||
else
|
||||
{
|
||||
//The main recurrent event has been changed, put it first and also change all the exception with the correct reccurence-id
|
||||
NSMutableArray *childrenNew;
|
||||
NSEnumerator *childrenOldEnum, *childrenNewEnum;
|
||||
iCalEvent *child;
|
||||
NSCalendarDate *oldStartDate, *newStartDate, *recId;
|
||||
NSTimeInterval interval;
|
||||
|
||||
oldStartDate = [calendarEvent startDate];
|
||||
newStartDate = [emailEvent startDate];
|
||||
interval = [newStartDate timeIntervalSinceDate: oldStartDate];
|
||||
|
||||
[parent removeChild: calendarEvent];
|
||||
childrenNew = [NSMutableArray array];
|
||||
childrenOldEnum = [[parent childrenWithTag:@"vevent"] objectEnumerator];
|
||||
while((child = [childrenOldEnum nextObject]))
|
||||
{
|
||||
[parent removeChild: child];
|
||||
if((recId = [child recurrenceId]))
|
||||
[child setRecurrenceId: [recId dateByAddingTimeInterval: interval]];
|
||||
[childrenNew addObject: child];
|
||||
}
|
||||
[parent addChild: emailEvent];
|
||||
childrenNewEnum = [childrenNew objectEnumerator];
|
||||
while((child = [childrenNewEnum nextObject]))
|
||||
{
|
||||
[parent addChild: child];
|
||||
}
|
||||
}
|
||||
|
||||
[*eventObject saveCalendar: parent];
|
||||
[*eventObject flush];
|
||||
chosenEvent = emailEvent;
|
||||
}
|
||||
else
|
||||
{
|
||||
chosenEvent = calendarEvent;
|
||||
if (![[[chosenEvent parent] method] length])
|
||||
[[chosenEvent parent] setMethod: [[emailEvent parent] method]];
|
||||
}
|
||||
}
|
||||
else
|
||||
chosenEvent = emailEvent;
|
||||
}
|
||||
|
||||
organizer = [chosenEvent organizer];
|
||||
if (![[organizer rfc822Email] length])
|
||||
[self _fixOrganizerInEvent: chosenEvent];
|
||||
}
|
||||
else
|
||||
chosenEvent = nil;
|
||||
|
||||
|
||||
@@ -171,6 +171,7 @@
|
||||
/* MailListView */
|
||||
"Sender" = "Sender";
|
||||
"Subject or Sender" = "Subject or Sender";
|
||||
"Subject or Recipient" = "Subject or Recipient";
|
||||
"To or Cc" = "To or Cc";
|
||||
"Entire Message" = "Entire Message";
|
||||
"Date" = "Date";
|
||||
@@ -514,3 +515,18 @@
|
||||
|
||||
/* Hotkey to forward to a message */
|
||||
"hotkey_forward" = "f";
|
||||
|
||||
/* Email deletion */
|
||||
"Clean folder" = "Clean folder";
|
||||
"Clean mailbox" = "Clean mailbox";
|
||||
"Delete permanently (do not use trash)" = "Delete permanently (do not use trash)";
|
||||
"Apply to subfolders" = "Apply to subfolders";
|
||||
"Delete e-mails older than" = "Delete e-mails older than";
|
||||
"3 months" = "3 months";
|
||||
"6 months" = "6 months";
|
||||
"9 months" = "9 months";
|
||||
"1 year" = "1 year";
|
||||
"Custom" = "Custom";
|
||||
"You are about to permanently delete some messages. Are you sure you want to proceed with this action?" = "You are about to permanently delete some messages. Are you sure you want to proceed with this action?";
|
||||
"Apply" = "Apply";
|
||||
"%{0} message(s) deleted" = "%{0} message(s) deleted";
|
||||
@@ -171,6 +171,7 @@
|
||||
/* MailListView */
|
||||
"Sender" = "Expéditeur";
|
||||
"Subject or Sender" = "Sujet ou expéditeur";
|
||||
"Subject or Recipient" = "Sujet ou destinataire";
|
||||
"To or Cc" = "Pour ou Copie à";
|
||||
"Entire Message" = "Message Complet";
|
||||
"Date" = "Date";
|
||||
@@ -514,3 +515,18 @@
|
||||
|
||||
/* Hotkey to forward to a message */
|
||||
"hotkey_forward" = "f";
|
||||
|
||||
/* Email deletion */
|
||||
"Clean folder" = "Nettoyer le dossier";
|
||||
"Clean mailbox" = "Nettoyer la boite mail";
|
||||
"Delete permanently (do not use trash)" = "Supprimer définitivement (ne pas utiliser la corbeille)";
|
||||
"Apply to subfolders" = "Appliquer aux sous-dossiers";
|
||||
"Delete e-mails older than" = "Supprimer les e-mails plus vieux de";
|
||||
"3 months" = "3 mois";
|
||||
"6 months" = "6 mois";
|
||||
"9 months" = "9 mois";
|
||||
"1 year" = "1 an";
|
||||
"Custom" = "Personnalisé";
|
||||
"You are about to permanently delete some messages. Are you sure you want to proceed with this action?" = "Vous êtes sur le point de supprimer définitivement certains messages. Êtes-vous certain de vouloir effectuer cette action ?";
|
||||
"Apply" = "Appliquer";
|
||||
"%{0} message(s) deleted" = "%{0} message(s) supprimés";
|
||||
@@ -27,6 +27,7 @@
|
||||
|
||||
#import <Foundation/NSData.h>
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSAutoreleasePool.h>
|
||||
|
||||
#import <NGHttp/NGHttpRequest.h>
|
||||
|
||||
@@ -60,11 +61,72 @@
|
||||
|
||||
@implementation UIxMailAccountActions
|
||||
|
||||
- (NSArray *)sortChildrenRecursively: (NSArray *)children {
|
||||
NSInteger i, j;
|
||||
NSMutableArray *sortedArray;
|
||||
NSArray *unsortedChildren, *sortedChildren;
|
||||
NSDictionary *child;
|
||||
NSMutableDictionary *mutableChild;
|
||||
NSString *name1, *name2;
|
||||
|
||||
if (nil == children || 0 == [children count]) {
|
||||
return children;
|
||||
}
|
||||
|
||||
sortedArray = [NSMutableArray array];
|
||||
|
||||
for (child in children) {
|
||||
unsortedChildren = [child objectForKey:@"children"];
|
||||
sortedChildren = [self sortChildrenRecursively:unsortedChildren];
|
||||
mutableChild = [child mutableCopy];
|
||||
[mutableChild setObject:sortedChildren forKey:@"children"];
|
||||
[sortedArray addObject:mutableChild];
|
||||
}
|
||||
|
||||
for (i = 0; i < [sortedArray count] - 1; i++) {
|
||||
for (j = i + 1; j < [sortedArray count]; j++) {
|
||||
name1 = [[sortedArray objectAtIndex:i] objectForKey:@"name"];
|
||||
name2 = [[sortedArray objectAtIndex:j] objectForKey:@"name"];
|
||||
if ([name1 compare:name2 options:NSCaseInsensitiveSearch] == NSOrderedDescending) {
|
||||
[sortedArray exchangeObjectAtIndex:i withObjectAtIndex:j];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return sortedArray;
|
||||
}
|
||||
|
||||
- (NSDictionary *)sortMailboxesRecursively: (NSDictionary *)data {
|
||||
NSArray *mailboxes, *mailboxChildren, *sortedMailboxChildren;
|
||||
NSMutableDictionary *sortedData, *mutableMailbox;
|
||||
NSMutableArray *sortedMailboxes;
|
||||
NSDictionary *mailbox;
|
||||
|
||||
mailboxes = [data objectForKey:@"mailboxes"];
|
||||
sortedData = [data mutableCopy];
|
||||
|
||||
sortedMailboxes = [NSMutableArray array];
|
||||
for (mailbox in mailboxes) {
|
||||
mutableMailbox = [mailbox mutableCopy];
|
||||
|
||||
mailboxChildren = [mailbox objectForKey:@"children"];
|
||||
sortedMailboxChildren = [self sortChildrenRecursively:mailboxChildren];
|
||||
|
||||
[mutableMailbox setObject:sortedMailboxChildren forKey:@"children"];
|
||||
[sortedMailboxes addObject:mutableMailbox];
|
||||
}
|
||||
|
||||
[sortedData setObject:sortedMailboxes forKey:@"mailboxes"];
|
||||
|
||||
return sortedData;
|
||||
}
|
||||
|
||||
- (WOResponse *) listMailboxesAction
|
||||
{
|
||||
SOGoMailAccount *co;
|
||||
NSArray *folders;
|
||||
NSDictionary *data;
|
||||
NSAutoreleasePool *pool;
|
||||
|
||||
co = [self clientObject];
|
||||
|
||||
@@ -74,6 +136,9 @@
|
||||
folders, @"mailboxes",
|
||||
[co getInboxQuota], @"quotas",
|
||||
nil];
|
||||
pool = [[NSAutoreleasePool alloc] init];
|
||||
data = [self sortMailboxesRecursively: data];
|
||||
DESTROY(pool);
|
||||
|
||||
return [self responseWithStatus: 200
|
||||
andJSONRepresentation: data];
|
||||
|
||||
@@ -132,6 +132,8 @@
|
||||
drafts = [account draftsFolderInContext: context];
|
||||
newMail = [drafts newDraft];
|
||||
[newMail fetchMailForEditing: co];
|
||||
if ([co isInTemplatesFolder])
|
||||
[newMail save];
|
||||
[newMail storeInfo];
|
||||
|
||||
accountName = [account nameInContainer];
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#import <Foundation/NSDictionary.h>
|
||||
#import <Foundation/NSURL.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <NGObjWeb/WOContext+SoObjects.h>
|
||||
#import <NGObjWeb/WORequest.h>
|
||||
@@ -46,6 +47,8 @@
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserSettings.h>
|
||||
|
||||
#import <EOControl/EOQualifier.h>
|
||||
|
||||
#import "UIxMailFolderActions.h"
|
||||
|
||||
@implementation UIxMailFolderActions
|
||||
@@ -1298,4 +1301,126 @@
|
||||
return [self _markMessagesAsJunkOrNotJunk: NO];
|
||||
}
|
||||
|
||||
- (BOOL)isDateStringValid: (NSString *)dateString {
|
||||
NSString *dateRegex = @"^\\d{4}-\\d{2}-\\d{2}$";
|
||||
NSPredicate *dateTest = [NSPredicate predicateWithFormat:@"SELF MATCHES %@", dateRegex];
|
||||
return [dateTest evaluateWithObject:dateString];
|
||||
}
|
||||
|
||||
- (void) cleanFolderWithFolder: (SOGoMailFolder *)folder
|
||||
withQualifier: (EOQualifier *)qualifier
|
||||
recursive: (BOOL)isRecursive
|
||||
withTrashFolder: (BOOL)useTrashFolder
|
||||
exception: (NSException **)exception
|
||||
counter: (NSUInteger *)counter {
|
||||
NSArray *results, *subfolders;
|
||||
NSUInteger i;
|
||||
SOGoMailFolder *newFolder;
|
||||
NSString *folderName;
|
||||
NSException *e;
|
||||
|
||||
results = [folder fetchUIDsMatchingQualifier: qualifier
|
||||
sortOrdering: @"REVERSE DATE"
|
||||
threaded: NO];
|
||||
|
||||
if (results && [results count] > 0) {
|
||||
e = [folder deleteUIDs: results
|
||||
useTrashFolder: &useTrashFolder
|
||||
inContext: [self context]];
|
||||
[folder expunge];
|
||||
[self logWithFormat: @"Removed %d messages in %@ (%@)", [results count], [folder nameInContainer], results];
|
||||
*counter += [results count];
|
||||
if (e) {
|
||||
[self errorWithFormat: @"Error while cleaning mailbox : %@", e];
|
||||
*exception = e;
|
||||
}
|
||||
}
|
||||
|
||||
if (isRecursive) {
|
||||
subfolders = [folder subfolders];
|
||||
for (i = 0 ; i < [subfolders count] ; i++) {
|
||||
folderName = [NSString stringWithFormat:@"%@/%@", [folder nameInContainer], [subfolders objectAtIndex: i]];
|
||||
newFolder = [[SOGoMailFolder alloc] initWithName: folderName inContainer: [folder container]];
|
||||
[self cleanFolderWithFolder: newFolder
|
||||
withQualifier: qualifier
|
||||
recursive: isRecursive
|
||||
withTrashFolder: useTrashFolder
|
||||
exception: exception
|
||||
counter: counter];
|
||||
[newFolder release];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (WOResponse *) cleanMailboxAction
|
||||
{
|
||||
NSDictionary *jsonRequest, *jsonResponse;
|
||||
WORequest *request;
|
||||
NSString *searchString, *folderName;
|
||||
EOQualifier *searchQualifier;
|
||||
BOOL isRecursive, useTrashFolder;
|
||||
NSException *exception;
|
||||
SOGoMailFolder *folder;
|
||||
SOGoMailAccount *account;
|
||||
NSUInteger i, counter;
|
||||
NSArray *folderNames;
|
||||
|
||||
request = [[self context] request];
|
||||
jsonRequest = [[request contentAsString] objectFromJSONString];
|
||||
|
||||
if (![[SOGoSystemDefaults sharedSystemDefaults] enableMailCleaning])
|
||||
return [self responseWithStatus: 401];
|
||||
|
||||
if (![self isDateStringValid: [jsonRequest objectForKey: @"date"]]) {
|
||||
[self errorWithFormat: @"Error while cleaning mailbox, invalid date : %@", [jsonRequest objectForKey: @"date"]];
|
||||
return [self responseWithStatus: 502];
|
||||
}
|
||||
|
||||
searchString = [NSString stringWithFormat: @"(NOT (flags = 'deleted') AND (DATE <= (NSCalendarDate)\"%@\"))", [jsonRequest objectForKey: @"date"]];
|
||||
searchQualifier = [EOQualifier qualifierWithQualifierFormat: searchString];
|
||||
isRecursive = [[jsonRequest objectForKey: @"applyToSubfolders"] boolValue];
|
||||
useTrashFolder = ![[jsonRequest objectForKey: @"permanentlyDelete"] boolValue];
|
||||
counter = 0;
|
||||
exception = nil;
|
||||
|
||||
if ([[self clientObject] isKindOfClass: [SOGoMailFolder class]]) {
|
||||
folder = [self clientObject];
|
||||
[self cleanFolderWithFolder: folder
|
||||
withQualifier: searchQualifier
|
||||
recursive: isRecursive
|
||||
withTrashFolder: useTrashFolder
|
||||
exception: &exception
|
||||
counter: &counter];
|
||||
} else if ([[self clientObject] isKindOfClass: [SOGoMailAccount class]]) {
|
||||
account = [self clientObject];
|
||||
folderNames = [account allFolderPaths: SOGoMailStandardListing onlyRoot: YES];
|
||||
for (i = 0 ; i < [folderNames count] ; i ++) {
|
||||
folderName = [folderNames objectAtIndex: i];
|
||||
if ([folderName hasPrefix:@"/"]) {
|
||||
folderName = [folderName substringFromIndex:1];
|
||||
}
|
||||
folder = [account folderWithTraversal: folderName andClassName: nil];
|
||||
// Disable clean for trash folder
|
||||
if (![folderName isEqualToString: [account trashFolderNameInContext: [self context]]]
|
||||
&& ![folderName isEqualToString: [account templatesFolderNameInContext: [self context]]])
|
||||
[self cleanFolderWithFolder: folder
|
||||
withQualifier: searchQualifier
|
||||
recursive: isRecursive
|
||||
withTrashFolder: useTrashFolder
|
||||
exception: &exception
|
||||
counter: &counter];
|
||||
}
|
||||
|
||||
[[account imap4Connection] flushMailCaches];
|
||||
}
|
||||
|
||||
if (exception) {
|
||||
return [self responseWithStatus: 500];
|
||||
}
|
||||
|
||||
jsonResponse = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: counter]
|
||||
forKey: @"nbMessageDeleted"];
|
||||
return [self responseWithStatus: 200 andJSONRepresentation: jsonResponse];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -466,6 +466,11 @@
|
||||
return sort;
|
||||
}
|
||||
|
||||
- (NSString *)sanitizeFilterString:(NSString *)str
|
||||
{
|
||||
return [str stringByReplacingOccurrencesOfString:@"\"" withString:@"\\\""];
|
||||
}
|
||||
|
||||
- (EOQualifier *) searchQualifier
|
||||
{
|
||||
EOQualifier *qualifier, *notDeleted, *searchQualifier;
|
||||
@@ -498,8 +503,8 @@
|
||||
for (i = 0; i < max; i++)
|
||||
{
|
||||
filter = [filters objectAtIndex:i];
|
||||
searchBy = [filter objectForKey: @"searchBy"];
|
||||
searchInput = [filter objectForKey: @"searchInput"];
|
||||
searchBy = [self sanitizeFilterString: [filter objectForKey: @"searchBy"]];
|
||||
searchInput = [self sanitizeFilterString:[filter objectForKey: @"searchInput"]];
|
||||
|
||||
if (searchBy && searchInput)
|
||||
{
|
||||
|
||||
@@ -611,6 +611,17 @@
|
||||
return result;
|
||||
}
|
||||
|
||||
- (BOOL) isCleanMailboxEnabled {
|
||||
BOOL result;
|
||||
SOGoSystemDefaults *sd;
|
||||
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
|
||||
result = [sd enableMailCleaning];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@end /* UIxMailMainFrame */
|
||||
|
||||
@interface UIxMailFolderTemplate : UIxComponent
|
||||
|
||||
@@ -49,6 +49,11 @@
|
||||
actionClass = "UIxMailFolderActions";
|
||||
actionName = "expunge";
|
||||
};
|
||||
cleanMailbox = {
|
||||
protectedBy = "View";
|
||||
actionClass = "UIxMailFolderActions";
|
||||
actionName = "cleanMailbox";
|
||||
};
|
||||
createFolder = {
|
||||
protectedBy = "View";
|
||||
actionClass = "UIxMailFolderActions";
|
||||
@@ -417,6 +422,11 @@
|
||||
protectedBy = "View";
|
||||
pageName = "UIxMailUserDelegationEditor";
|
||||
};
|
||||
cleanMailbox = {
|
||||
protectedBy = "View";
|
||||
actionClass = "UIxMailFolderActions";
|
||||
actionName = "cleanMailbox";
|
||||
};
|
||||
addDelegate = {
|
||||
protectedBy = "View";
|
||||
actionClass = "UIxMailAccountActions";
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"Domain" = "Domain";
|
||||
"Remember username" = "Remember username";
|
||||
"Connect" = "Connect";
|
||||
"Create an account" = "Create an account";
|
||||
|
||||
/* Appears while authentication is in progress */
|
||||
"Authenticating" = "Authenticating";
|
||||
@@ -87,6 +88,7 @@ See <a href=\"http://www.sogo.nu/en/support/community.html\">this page</a> for v
|
||||
"Change your Password" = "Change your Password";
|
||||
"The password was changed successfully." = "The password was changed successfully.";
|
||||
"Your password has expired, please enter a new one below" = "Your password has expired, please enter a new one below";
|
||||
"Your password is too weak. Please choose a stronger password to enhance your security" = "Your password is too weak. Please choose a stronger password to enhance your security";
|
||||
"Password must not be empty." = "Password must not be empty.";
|
||||
"The passwords do not match. Please try again." = "The passwords do not match. Please try again.";
|
||||
"Password Grace Period" = "Password Grace Period";
|
||||
@@ -129,4 +131,4 @@ See <a href=\"http://www.sogo.nu/en/support/community.html\">this page</a> for v
|
||||
"Invalid configuration for email password recovery" = "Invalid configuration for email password recovery";
|
||||
"Password recovery email in error" = "Password recovery email in error";
|
||||
"Password reset" = "Password reset";
|
||||
"Hi %{0},\nThere was a request to change your password!\n\nIf you did not make this request then please ignore this email.\n\nOtherwise, please click this link to change your password: %{1}" = "Hi %{0},\nThere was a request to change your password!\n\nIf you did not make this request then please ignore this email.\n\nOtherwise, please click this link to change your password: %{1}";
|
||||
"Hi %{0},\nThere was a request to change your password!\n\nIf you did not make this request then please ignore this email.\n\nOtherwise, please click this link to change your password: %{1}" = "Hi %{0},\nThere was a request to change your password!\n\nIf you did not make this request then please ignore this email.\n\nOtherwise, please click this link to change your password: %{1}";
|
||||
@@ -6,6 +6,7 @@
|
||||
"Domain" = "Domaine";
|
||||
"Remember username" = "Se souvenir de moi";
|
||||
"Connect" = "Connexion";
|
||||
"Create an account" = "Créer un compte";
|
||||
|
||||
/* Appears while authentication is in progress */
|
||||
"Authenticating" = "Authentification en cours";
|
||||
@@ -84,6 +85,7 @@
|
||||
"Change your Password" = "Changez votre mot de passe";
|
||||
"The password was changed successfully." = "Votre mot de passe a bien été changé.";
|
||||
"Your password has expired, please enter a new one below" = "Votre mot de passe est expiré, veuillez entrer un nouveau mot de passe";
|
||||
"Your password is too weak. Please choose a stronger password to enhance your security" = "Votre mot de passe est trop faible. Veuillez choisir un mot de passe plus sécurisé pour renforcer votre protection";
|
||||
"Password must not be empty." = "Le mot de passe ne doit pas être vide.";
|
||||
"The passwords do not match. Please try again." = "Les mots de passe ne sont pas identiques. Essayez de nouveau.";
|
||||
"Password Grace Period" = "Période de grâce pour le mot de passe";
|
||||
@@ -126,4 +128,4 @@
|
||||
"Invalid configuration for email password recovery" = "Configuration invalide pour la récupération de mot de passe par e-mail";
|
||||
"Password recovery email in error" = "Erreur lors de l'envoi de l'email de récupération";
|
||||
"Password reset" = "Réinitialisation de mot de passe";
|
||||
"Hi %{0},\nThere was a request to change your password!\n\nIf you did not make this request then please ignore this email.\n\nOtherwise, please click this link to change your password: %{1}" = "Bonjour %{0},\nUne demande de changement de mot de passe a été initiée.\n\nSi vous n'êtes pas à l'origine de cet e-mail, n'en tenez pas compte.\n\nSi vous en êtes bien à l'origine, veuillez cliquer sur le lien ci-dessous pour modifier votre mot de passe: %{1}";
|
||||
"Hi %{0},\nThere was a request to change your password!\n\nIf you did not make this request then please ignore this email.\n\nOtherwise, please click this link to change your password: %{1}" = "Bonjour %{0},\nUne demande de changement de mot de passe a été initiée.\n\nSi vous n'êtes pas à l'origine de cet e-mail, n'en tenez pas compte.\n\nSi vous en êtes bien à l'origine, veuillez cliquer sur le lien ci-dessous pour modifier votre mot de passe: %{1}";
|
||||
+484
-78
@@ -40,6 +40,7 @@
|
||||
#import <SOGo/SOGoBuild.h>
|
||||
#import <SOGo/SOGoCache.h>
|
||||
#import <SOGo/SOGoCASSession.h>
|
||||
#import <SOGo/SOGoOpenIdSession.h>
|
||||
#if defined(SAML2_CONFIG)
|
||||
#import <SOGo/SOGoSAML2Session.h>
|
||||
#endif /* SAML2_ENABLE */
|
||||
@@ -52,6 +53,7 @@
|
||||
#import <SOGo/SOGoEmptyAuthenticator.h>
|
||||
#import <SOGo/SOGoMailer.h>
|
||||
#import <SOGo/SOGoAdmin.h>
|
||||
#import <SOGo/SOGoPasswordPolicy.h>
|
||||
|
||||
#if defined(MFA_CONFIG)
|
||||
#include <liboath/oath.h>
|
||||
@@ -144,6 +146,7 @@ static const NSString *kJwtKey = @"jwt";
|
||||
|
||||
- (WOCookie *) _authLocationCookie: (BOOL) cookieReset
|
||||
withName: (NSString *) cookieName
|
||||
withValue: (NSString *) _value
|
||||
{
|
||||
WOCookie *locationCookie;
|
||||
NSString *appName;
|
||||
@@ -151,7 +154,10 @@ static const NSString *kJwtKey = @"jwt";
|
||||
NSCalendarDate *date;
|
||||
|
||||
rq = [context request];
|
||||
locationCookie = [WOCookie cookieWithName: cookieName value: [rq uri]];
|
||||
if(_value)
|
||||
locationCookie = [WOCookie cookieWithName: cookieName value: _value];
|
||||
else
|
||||
locationCookie = [WOCookie cookieWithName: cookieName value: [rq uri]];
|
||||
appName = [rq applicationName];
|
||||
[locationCookie setPath: [NSString stringWithFormat: @"/%@/", appName]];
|
||||
if (cookieReset)
|
||||
@@ -164,17 +170,48 @@ static const NSString *kJwtKey = @"jwt";
|
||||
return locationCookie;
|
||||
}
|
||||
|
||||
- (WOCookie *) _domainCookie: (BOOL) cookieReset
|
||||
withDomain: (NSString *) _domain
|
||||
{
|
||||
WOCookie *domainCookie;
|
||||
NSString *appName;
|
||||
WORequest *rq;
|
||||
NSCalendarDate *date;
|
||||
|
||||
rq = [context request];
|
||||
domainCookie = [WOCookie cookieWithName: @"sogo-user-domain" value: _domain];
|
||||
appName = [rq applicationName];
|
||||
[domainCookie setPath: [NSString stringWithFormat: @"/%@/", appName]];
|
||||
if (cookieReset)
|
||||
{
|
||||
date = [NSCalendarDate calendarDate];
|
||||
[date setTimeZone: [NSTimeZone timeZoneForSecondsFromGMT: 0]];
|
||||
[domainCookie setExpires: [date yesterday]];
|
||||
}
|
||||
|
||||
return domainCookie;
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (WOResponse *) _responseWithLDAPPolicyError: (int) error
|
||||
- (WOResponse *) _responseWithLDAPPolicyError: (int) error additionalInfos: (NSDictionary *) additionalInfos
|
||||
{
|
||||
NSDictionary *jsonError;
|
||||
|
||||
jsonError = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: error]
|
||||
forKey: @"LDAPPasswordPolicyError"];
|
||||
return [self responseWithStatus: 403
|
||||
andJSONRepresentation: jsonError];
|
||||
if (additionalInfos) {
|
||||
jsonError = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithInt:error], @"LDAPPasswordPolicyError",
|
||||
additionalInfos, @"additionalInfos",
|
||||
nil];
|
||||
} else {
|
||||
jsonError = [NSDictionary dictionaryWithObjectsAndKeys:
|
||||
[NSNumber numberWithInt:error], @"LDAPPasswordPolicyError",
|
||||
nil];
|
||||
}
|
||||
|
||||
return [self responseWithStatus:403
|
||||
andJSONRepresentation:jsonError];
|
||||
}
|
||||
|
||||
- (void) _checkAutoReloadWebCalendars: (SOGoUser *) loggedInUser
|
||||
@@ -197,6 +234,27 @@ static const NSString *kJwtKey = @"jwt";
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
- (void)translateAdditionalLoginInformations:(NSMutableDictionary **)additionalLoginInformations
|
||||
{
|
||||
NSDictionary *policy;
|
||||
NSMutableDictionary *translations;
|
||||
|
||||
if (additionalLoginInformations && *additionalLoginInformations) {
|
||||
if ([*additionalLoginInformations objectForKey:@"userPolicies"]) {
|
||||
translations = [[NSMutableDictionary alloc] init];
|
||||
for (policy in [*additionalLoginInformations objectForKey:@"userPolicies"]) {
|
||||
[translations setObject:[self commonLabelForKey: [policy objectForKey:@"label"]] forKey: [policy objectForKey:@"label"]];
|
||||
}
|
||||
[*additionalLoginInformations setObject:[SOGoPasswordPolicy createPasswordPolicyLabels: [*additionalLoginInformations objectForKey:@"userPolicies"] withTranslations: translations]
|
||||
forKey:@"userPolicies"];
|
||||
[translations release];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
@@ -210,6 +268,7 @@ static const NSString *kJwtKey = @"jwt";
|
||||
SOGoUserSettings *us;
|
||||
SOGoUser *loggedInUser;
|
||||
NSDictionary *params;
|
||||
NSMutableDictionary *additionalLoginInformations;
|
||||
NSString *username, *password, *language, *domain, *remoteHost;
|
||||
NSArray *supportedLanguages, *creds;
|
||||
|
||||
@@ -223,6 +282,7 @@ static const NSString *kJwtKey = @"jwt";
|
||||
auth = [[WOApplication application] authenticatorInContext: context];
|
||||
request = [context request];
|
||||
params = [[request contentAsString] objectFromJSONString];
|
||||
additionalLoginInformations = [[NSMutableDictionary alloc] init];
|
||||
|
||||
username = [params objectForKey: @"userName"];
|
||||
password = [params objectForKey: @"password"];
|
||||
@@ -232,9 +292,11 @@ static const NSString *kJwtKey = @"jwt";
|
||||
/* this will always be set to something more or less useful by
|
||||
* [WOHttpTransaction applyAdaptorHeadersWithHttpRequest] */
|
||||
remoteHost = [request headerForKey:@"x-webobjects-remote-host"];
|
||||
b = [auth checkLogin: username password: password domain: &domain
|
||||
perr: &err expire: &expire grace: &grace additionalInfo: &additionalLoginInformations useCache: NO];
|
||||
[self translateAdditionalLoginInformations: &additionalLoginInformations];
|
||||
|
||||
if ((b = [auth checkLogin: username password: password domain: &domain
|
||||
perr: &err expire: &expire grace: &grace useCache: NO])
|
||||
if (b
|
||||
&& (err == PolicyNoError)
|
||||
// no password policy
|
||||
&& ((expire < 0 && grace < 0) // no password policy or everything is alright
|
||||
@@ -334,7 +396,7 @@ static const NSString *kJwtKey = @"jwt";
|
||||
#endif
|
||||
|
||||
if ([us objectForKey: @"ForceResetPassword"]) {
|
||||
response = [self _responseWithLDAPPolicyError: PolicyPasswordExpired];
|
||||
response = [self _responseWithLDAPPolicyError: PolicyPasswordExpired additionalInfos: additionalLoginInformations];
|
||||
} else {
|
||||
[self _checkAutoReloadWebCalendars: loggedInUser];
|
||||
|
||||
@@ -377,7 +439,7 @@ static const NSString *kJwtKey = @"jwt";
|
||||
[self logWithFormat: @"Login from '%@' for user '%@' might not have worked - password policy: %d grace: %d expire: %d bound: %d",
|
||||
remoteHost, username, err, grace, expire, b];
|
||||
|
||||
response = [self _responseWithLDAPPolicyError: err];
|
||||
response = [self _responseWithLDAPPolicyError: err additionalInfos: additionalLoginInformations];
|
||||
}
|
||||
|
||||
if (rememberLogin)
|
||||
@@ -385,9 +447,12 @@ static const NSString *kJwtKey = @"jwt";
|
||||
else
|
||||
[response addCookie: [self _cookieWithUsername: nil]];
|
||||
|
||||
[additionalLoginInformations release];
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
- (NSDictionary *) _casRedirectKeys
|
||||
{
|
||||
NSDictionary *redirectKeys;
|
||||
@@ -441,42 +506,43 @@ static const NSString *kJwtKey = @"jwt";
|
||||
if ([login isEqualToString: @"anonymous"])
|
||||
login = nil;
|
||||
if (!login)
|
||||
{
|
||||
rq = [context request];
|
||||
ticket = [rq formValueForKey: @"ticket"];
|
||||
if ([ticket length])
|
||||
{
|
||||
rq = [context request];
|
||||
ticket = [rq formValueForKey: @"ticket"];
|
||||
if ([ticket length])
|
||||
{
|
||||
casSession = [SOGoCASSession CASSessionWithTicket: ticket
|
||||
fromProxy: NO];
|
||||
login = [casSession login];
|
||||
if ([login length])
|
||||
{
|
||||
auth = [[WOApplication application]
|
||||
authenticatorInContext: context];
|
||||
casCookie = [auth cookieWithUsername: login
|
||||
andPassword: [casSession identifier]
|
||||
inContext: context];
|
||||
[casSession updateCache];
|
||||
newLocation = [rq cookieValueForKey: @"cas-location"];
|
||||
/* login callback, we expire the "cas-location" cookie, created
|
||||
below */
|
||||
casLocationCookie = [self _authLocationCookie: YES
|
||||
withName: @"cas-location"];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* anonymous and no ticket, possibly a logout request from CAS
|
||||
* See: https://wiki.jasig.org/display/CASUM/Single+Sign+Out
|
||||
*/
|
||||
logoutRequest = [rq formValueForKey: @"logoutRequest"];
|
||||
if ([logoutRequest length])
|
||||
{
|
||||
[SOGoCASSession handleLogoutRequest: logoutRequest];
|
||||
return [self responseWithStatus: 200];
|
||||
}
|
||||
}
|
||||
casSession = [SOGoCASSession CASSessionWithTicket: ticket
|
||||
fromProxy: NO];
|
||||
login = [casSession login];
|
||||
if ([login length])
|
||||
{
|
||||
auth = [[WOApplication application]
|
||||
authenticatorInContext: context];
|
||||
casCookie = [auth cookieWithUsername: login
|
||||
andPassword: [casSession identifier]
|
||||
inContext: context];
|
||||
[casSession updateCache];
|
||||
newLocation = [rq cookieValueForKey: @"cas-location"];
|
||||
/* login callback, we expire the "cas-location" cookie, created
|
||||
below */
|
||||
casLocationCookie = [self _authLocationCookie: YES
|
||||
withName: @"cas-location"
|
||||
withValue: nil];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/* anonymous and no ticket, possibly a logout request from CAS
|
||||
* See: https://wiki.jasig.org/display/CASUM/Single+Sign+Out
|
||||
*/
|
||||
logoutRequest = [rq formValueForKey: @"logoutRequest"];
|
||||
if ([logoutRequest length])
|
||||
{
|
||||
[SOGoCASSession handleLogoutRequest: logoutRequest];
|
||||
return [self responseWithStatus: 200];
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
ticket = nil;
|
||||
|
||||
@@ -500,7 +566,8 @@ static const NSString *kJwtKey = @"jwt";
|
||||
newLocation = [SOGoCASSession CASURLWithAction: @"login"
|
||||
andParameters: [self _casRedirectKeys]];
|
||||
casLocationCookie = [self _authLocationCookie: NO
|
||||
withName: @"cas-location"];
|
||||
withName: @"cas-location"
|
||||
withValue: nil];
|
||||
}
|
||||
response = [self redirectToLocation: newLocation];
|
||||
if (casCookie)
|
||||
@@ -511,6 +578,131 @@ static const NSString *kJwtKey = @"jwt";
|
||||
return response;
|
||||
}
|
||||
|
||||
- (id <WOActionResults>) _openidDefaultAction: (NSString *) _domain
|
||||
{
|
||||
WOResponse *response;
|
||||
NSString *login, *redirectLocation, *serverUrl;
|
||||
NSString *sessionState, *code, *refreshTokenValue;
|
||||
NSURL *newLocation, *oldLocation;
|
||||
NSDictionary *formValues;
|
||||
SOGoUser *loggedInUser;
|
||||
WOCookie *openIdCookie, *openIdCookieLocation, *openIdRefreshCookie, *domainCookie;
|
||||
WORequest *rq;
|
||||
SOGoWebAuthenticator *auth;
|
||||
SOGoOpenIdSession *openIdSession;
|
||||
id value;
|
||||
|
||||
openIdCookie = nil;
|
||||
openIdCookieLocation = nil;
|
||||
openIdRefreshCookie = nil;
|
||||
domainCookie = nil;
|
||||
newLocation = nil;
|
||||
|
||||
rq = [context request];
|
||||
|
||||
//Check if the domain is stored in a cookie if not given
|
||||
if(_domain == nil || [_domain length] == 0)
|
||||
_domain = [rq cookieValueForKey: @"sogo-user-domain"]; //_domain can still be nil aftert his
|
||||
|
||||
openIdSession = [SOGoOpenIdSession OpenIdSession: _domain];
|
||||
|
||||
if(![openIdSession sessionIsOk])
|
||||
{
|
||||
return [NSException exceptionWithHTTPStatus: 502 /* Bad OpenId Configuration */
|
||||
reason: [NSString stringWithFormat: @"OpenId server not found or has unexpected behavior, contact your admin."]];
|
||||
}
|
||||
|
||||
login = [[context activeUser] login];
|
||||
if ([login isEqualToString: @"anonymous"])
|
||||
login = nil;
|
||||
if (!login)
|
||||
{
|
||||
//You get here if you nerver been logged in or if you token is expired
|
||||
serverUrl = [[context serverURL] absoluteString];
|
||||
redirectLocation = [NSString stringWithFormat: @"%@/%@/", serverUrl, [rq applicationName]];
|
||||
if((formValues = [rq formValues]) && [formValues objectForKey: @"code"])
|
||||
{
|
||||
//You get here if this is the callback of openid after you logged in
|
||||
|
||||
//NOT MANDATORY
|
||||
// value = [formValues objectForKey: @"session_state"];
|
||||
// if ([value isKindOfClass: [NSArray class]])
|
||||
// sessionState = [value lastObject];
|
||||
// else
|
||||
// sessionState = value;
|
||||
|
||||
value = [formValues objectForKey: @"code"];
|
||||
if ([value isKindOfClass: [NSArray class]])
|
||||
code = [value lastObject];
|
||||
else
|
||||
code = value;
|
||||
[openIdSession fetchToken: code redirect: redirectLocation];
|
||||
login = [openIdSession login: @""];
|
||||
if ([login length])
|
||||
{
|
||||
auth = [[WOApplication application] authenticatorInContext: context];
|
||||
openIdCookie = [auth cookieWithUsername: login
|
||||
andPassword: [openIdSession getToken]
|
||||
inContext: context];
|
||||
}
|
||||
newLocation = [rq cookieValueForKey: @"openid-location"];
|
||||
openIdCookieLocation = [self _authLocationCookie: YES withName: @"openid-location" withValue: nil];
|
||||
domainCookie = [self _domainCookie: YES withDomain: _domain];
|
||||
}
|
||||
// else if((formValues = [rq formValues]) && [formValues objectForKey: @"action"])
|
||||
// {
|
||||
// value = [formValues objectForKey: @"action"];
|
||||
// if ([value isKindOfClass: [NSArray class]])
|
||||
// code = [value lastObject];
|
||||
// else
|
||||
// code = value;
|
||||
// if([code isEqualToString:@"redirect"])
|
||||
// {
|
||||
// //this action onlye serve to make a redirection to openId server after a GET request
|
||||
// newLocation = [openIdSession loginUrl: redirectLocation];
|
||||
// }
|
||||
//}
|
||||
else
|
||||
{
|
||||
// //You get here the first time you access sogo, it redirect to last location
|
||||
// if([[rq method] isEqualToString: @"POST"])
|
||||
// //To avoid making a redirection to openid server after a post request, we first redirect to a get method
|
||||
// newLocation = [NSString stringWithFormat: @"%@?action=redirect", redirectLocation];
|
||||
// else
|
||||
if(_domain != nil && [_domain length] > 0)
|
||||
{
|
||||
//add the domain cookie to get it after the redirect
|
||||
domainCookie = [self _domainCookie: NO withDomain: _domain];
|
||||
}
|
||||
newLocation = [openIdSession loginUrl: redirectLocation];
|
||||
openIdCookieLocation = [self _authLocationCookie: NO withName: @"openid-location" withValue: nil];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!newLocation)
|
||||
{
|
||||
oldLocation = [[self clientObject] baseURLInContext: context];
|
||||
newLocation = [NSString stringWithFormat: @"%@%@", oldLocation, [login stringByEscapingURL]];
|
||||
}
|
||||
|
||||
loggedInUser = [SOGoUser userWithLogin: login];
|
||||
[self _checkAutoReloadWebCalendars: loggedInUser];
|
||||
}
|
||||
|
||||
response = [self redirectToLocation: newLocation];
|
||||
if (openIdCookie)
|
||||
[response addCookie: openIdCookie];
|
||||
if (openIdCookieLocation)
|
||||
[response addCookie: openIdCookieLocation];
|
||||
if(domainCookie)
|
||||
[response addCookie: domainCookie];
|
||||
//[response setStatus: 303];
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#if defined(SAML2_CONFIG)
|
||||
- (id <WOActionResults>) _saml2DefaultAction
|
||||
{
|
||||
@@ -534,7 +726,8 @@ static const NSString *kJwtKey = @"jwt";
|
||||
newLocation = [rq cookieValueForKey: @"saml2-location"];
|
||||
if (newLocation)
|
||||
saml2LocationCookie = [self _authLocationCookie: YES
|
||||
withName: @"saml2-location"];
|
||||
withName: @"saml2-location"
|
||||
withValue: nil];
|
||||
else
|
||||
{
|
||||
oldLocation = [[self clientObject] baseURLInContext: context];
|
||||
@@ -549,7 +742,8 @@ static const NSString *kJwtKey = @"jwt";
|
||||
{
|
||||
newLocation = [SOGoSAML2Session authenticationURLInContext: context];
|
||||
saml2LocationCookie = [self _authLocationCookie: NO
|
||||
withName: @"saml2-location"];
|
||||
withName: @"saml2-location"
|
||||
withValue: nil];
|
||||
}
|
||||
|
||||
response = [self redirectToLocation: newLocation];
|
||||
@@ -570,43 +764,191 @@ static const NSString *kJwtKey = @"jwt";
|
||||
login = nil;
|
||||
|
||||
if (login)
|
||||
{
|
||||
oldLocation = [[self clientObject] baseURLInContext: context];
|
||||
response
|
||||
= [self redirectToLocation: [NSString stringWithFormat: @"%@%@",
|
||||
oldLocation,
|
||||
[[SOGoUser getEncryptedUsernameIfNeeded:login request: [context request]] stringByEscapingURL]]];
|
||||
}
|
||||
{
|
||||
oldLocation = [[self clientObject] baseURLInContext: context];
|
||||
response = [self redirectToLocation: [NSString stringWithFormat: @"%@%@", oldLocation,
|
||||
[[SOGoUser getEncryptedUsernameIfNeeded:login request: [context request]] stringByEscapingURL]]];
|
||||
}
|
||||
else
|
||||
{
|
||||
oldLocation = [[context request] uri];
|
||||
if ([context clientObject]
|
||||
&& ![oldLocation hasSuffix: @"/"]
|
||||
&& ![oldLocation hasSuffix: @"/view"])
|
||||
response = [self redirectToLocation:
|
||||
[NSString stringWithFormat: @"%@/", oldLocation]];
|
||||
else
|
||||
response = self;
|
||||
}
|
||||
{
|
||||
oldLocation = [[context request] uri];
|
||||
if ([context clientObject] && ![oldLocation hasSuffix: @"/"] && ![oldLocation hasSuffix: @"/view"])
|
||||
response = [self redirectToLocation: [NSString stringWithFormat: @"%@/", oldLocation]];
|
||||
else
|
||||
response = self;
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
- (WOResponse *) connectNameAction
|
||||
{
|
||||
WOResponse *response;
|
||||
WORequest *request;
|
||||
NSDictionary *params;
|
||||
NSString *username, *language, *domain, *type, *serverUrl, *redirectLocation;
|
||||
NSRange r;
|
||||
|
||||
request = [context request];
|
||||
params = [[request contentAsString] objectFromJSONString];
|
||||
|
||||
username = [params objectForKey: @"userName"];
|
||||
|
||||
//Extract the domain
|
||||
r = [username rangeOfString: @"@"];
|
||||
if (r.location != NSNotFound)
|
||||
{
|
||||
domain = [username substringFromIndex: r.location+1];
|
||||
type = [[SOGoSystemDefaults sharedSystemDefaults] getLoginTypeForDomain: domain];
|
||||
if(type != nil)
|
||||
{
|
||||
if([type isEqualToString: @"plain"])
|
||||
{
|
||||
//Only reload the page with the name
|
||||
serverUrl = [[context serverURL] absoluteString];
|
||||
redirectLocation = [NSString stringWithFormat: @"%@/%@/login?hint=%@", serverUrl, [request applicationName], username];
|
||||
//response = [self redirectToLocation: [NSString stringWithFormat: @"%@/", redirectLocation]];
|
||||
response = [self responseWithStatus: 200 andJSONRepresentation:
|
||||
[NSDictionary dictionaryWithObjectsAndKeys: redirectLocation, @"redirect", nil]];
|
||||
}
|
||||
else if([type isEqualToString: @"openid"])
|
||||
{
|
||||
SOGoOpenIdSession *openIdSession;
|
||||
WOCookie *domainCookie, *openIdCookieLocation;
|
||||
|
||||
//With openId, the user will be redirected to the openid server for login
|
||||
//With set the domain in a cookie to know it after the openid does the callbacl
|
||||
serverUrl = [[context serverURL] absoluteString];
|
||||
redirectLocation = [NSString stringWithFormat: @"%@/%@/", serverUrl, [request applicationName]];
|
||||
|
||||
openIdSession = [SOGoOpenIdSession OpenIdSession: domain];
|
||||
|
||||
domainCookie = [self _domainCookie: NO withDomain: domain];
|
||||
openIdCookieLocation = [self _authLocationCookie: NO withName: @"openid-location" withValue: redirectLocation];
|
||||
|
||||
response = [self responseWithStatus: 200 andJSONRepresentation:
|
||||
[NSDictionary dictionaryWithObjectsAndKeys: [openIdSession loginUrl: redirectLocation], @"redirect", nil]];
|
||||
[response addCookie: domainCookie];
|
||||
[response addCookie: openIdCookieLocation];
|
||||
}
|
||||
else if([type isEqualToString: @"cas"] || [type isEqualToString: @"saml2"])
|
||||
{
|
||||
[self logWithFormat: @"Unsupported type for now: %@", type];
|
||||
response = [self responseWithStatus: 400
|
||||
andString: @"Domain Authentication type not supported"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"Unknown type: %@", type];
|
||||
response = [self responseWithStatus: 400
|
||||
andString: @"Unknwon Authentication type"];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"Auth type for Domain given is not set or there is no default value: %@", domain];
|
||||
response = [self responseWithStatus: 400
|
||||
andString: @"Domain unknown"];
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"Domain is required but not found for user recovery exception for user %@", username];
|
||||
response = [self responseWithStatus: 400
|
||||
andString: @"Domain needed in the login"];
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
- (id <WOActionResults>) defaultAction
|
||||
{
|
||||
NSString *authenticationType;
|
||||
NSString *authenticationType, *loginDomain, *type, *_domain;
|
||||
SOGoSystemDefaults* sd;
|
||||
id <WOActionResults> result;
|
||||
|
||||
authenticationType = [[SOGoSystemDefaults sharedSystemDefaults]
|
||||
authenticationType];
|
||||
if ([authenticationType isEqualToString: @"cas"])
|
||||
result = [self _casDefaultAction];
|
||||
#if defined(SAML2_CONFIG)
|
||||
else if ([authenticationType isEqualToString: @"saml2"])
|
||||
result = [self _saml2DefaultAction];
|
||||
#endif /* SAML2_CONFIG */
|
||||
else
|
||||
result = [self _standardDefaultAction];
|
||||
loginDomain = nil;
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
if([sd doesLoginTypeByDomain])
|
||||
{
|
||||
NSString *login;
|
||||
//In this mode sogo will ask the mail of the user before doing any authentication
|
||||
//Check if a user is already logged in
|
||||
|
||||
_domain = [[context request] cookieValueForKey: @"sogo-user-domain"]; //_domain can still be nil aftert his
|
||||
if(_domain != nil)
|
||||
{
|
||||
//This is a callback of an openid session.
|
||||
return [self _openidDefaultAction: _domain];
|
||||
}
|
||||
|
||||
login = [[context activeUser] login];
|
||||
if ([login isEqualToString: @"anonymous"])
|
||||
login = nil;
|
||||
if(!login && !_domain)
|
||||
return [self _standardDefaultAction];
|
||||
else
|
||||
{
|
||||
//User already logged in. Extract the domain in that case
|
||||
NSRange r;
|
||||
r = [login rangeOfString: @"@"];
|
||||
if (r.location != NSNotFound)
|
||||
{
|
||||
loginDomain = [login substringFromIndex: r.location+1];
|
||||
type = [sd getLoginTypeForDomain: loginDomain];
|
||||
if(type)
|
||||
{
|
||||
if([type isEqualToString: @"plain"])
|
||||
{
|
||||
result = [self _standardDefaultAction];
|
||||
}
|
||||
else if([type isEqualToString: @"openid"])
|
||||
{
|
||||
result = [self _openidDefaultAction: loginDomain];
|
||||
}
|
||||
else if([type isEqualToString: @"cas"] || [type isEqualToString: @"saml2"])
|
||||
{
|
||||
[self logWithFormat: @"Unsupported type for now: %@", type];
|
||||
result = [self responseWithStatus: 400
|
||||
andString: @"Domain Authentication type not supported"];
|
||||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"Unknown type: %@", type];
|
||||
result = [self responseWithStatus: 400
|
||||
andString: @"Unknwon Authentication type"];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"Auth type for Domain given is not set or there is no default value: %@", loginDomain];
|
||||
result = [self responseWithStatus: 400
|
||||
andString: @"Domain unknown"];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
loginDomain = nil;
|
||||
result = [self _standardDefaultAction];
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
authenticationType = [sd authenticationType];
|
||||
|
||||
if ([authenticationType isEqualToString: @"cas"])
|
||||
result = [self _casDefaultAction];
|
||||
else if ([authenticationType isEqualToString: @"openid"])
|
||||
result = [self _openidDefaultAction: loginDomain];
|
||||
#if defined(SAML2_CONFIG)
|
||||
else if ([authenticationType isEqualToString: @"saml2"])
|
||||
result = [self _saml2DefaultAction];
|
||||
#endif /* SAML2_CONFIG */
|
||||
else
|
||||
result = [self _standardDefaultAction];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -636,6 +978,57 @@ static const NSString *kJwtKey = @"jwt";
|
||||
return ([[self loginDomains] count] > 0);
|
||||
}
|
||||
|
||||
- (BOOL) doLoginUsernameFirst
|
||||
{
|
||||
return [[SOGoSystemDefaults sharedSystemDefaults] doesLoginTypeByDomain];
|
||||
}
|
||||
|
||||
- (BOOL) doFullLogin
|
||||
{
|
||||
//Either we directly do the full login (meaning the user inputs its username and password)
|
||||
//Or we do it in two times:
|
||||
//phase 1: user types its username first -> only show the username input
|
||||
//phase 2: user types its password -> show all inputs
|
||||
//In phase 2, the username will be in the query at key "login"
|
||||
if([self doLoginUsernameFirst]){
|
||||
WORequest *rq;
|
||||
BOOL hasLogin;
|
||||
NSDictionary *formValues;
|
||||
|
||||
rq = [context request];
|
||||
hasLogin = ((formValues=[rq formValues]) && [formValues objectForKey: @"hint"]);
|
||||
return hasLogin;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (BOOL) doPartialLogin
|
||||
{
|
||||
return ![self doFullLogin];
|
||||
}
|
||||
|
||||
|
||||
- (NSString *) getLoginHint
|
||||
{
|
||||
id value;
|
||||
WORequest *rq;
|
||||
NSString* login;
|
||||
NSDictionary *formValues;
|
||||
|
||||
login = @"";
|
||||
|
||||
rq = [context request];
|
||||
if((formValues=[rq formValues]) && (value=[formValues objectForKey: @"hint"]))
|
||||
{
|
||||
if ([value isKindOfClass: [NSArray class]])
|
||||
login = [value lastObject];
|
||||
else
|
||||
login = value;
|
||||
}
|
||||
return login;
|
||||
}
|
||||
|
||||
- (BOOL) hasPasswordRecovery
|
||||
{
|
||||
return [[SOGoSystemDefaults sharedSystemDefaults] isPasswordRecoveryEnabled];
|
||||
@@ -808,7 +1201,7 @@ static const NSString *kJwtKey = @"jwt";
|
||||
}
|
||||
}
|
||||
else
|
||||
response = [self _responseWithLDAPPolicyError: error];
|
||||
response = [self _responseWithLDAPPolicyError: error additionalInfos: nil];
|
||||
}
|
||||
|
||||
return response;
|
||||
@@ -999,8 +1392,7 @@ static const NSString *kJwtKey = @"jwt";
|
||||
message = [[request contentAsString] objectFromJSONString];
|
||||
username = [message objectForKey: @"userName"];
|
||||
domain = [message objectForKey: @"domain"];
|
||||
if ([[SOGoSystemDefaults sharedSystemDefaults]
|
||||
isPasswordRecoveryEnabled]) {
|
||||
if ([[SOGoSystemDefaults sharedSystemDefaults] isPasswordRecoveryEnabled]) {
|
||||
// If no domain, try to retrieve domain from username
|
||||
if (nil != domain && domain != [NSNull null]) {
|
||||
domainName = domain;
|
||||
@@ -1060,4 +1452,18 @@ static const NSString *kJwtKey = @"jwt";
|
||||
return [[SOGoAdmin sharedInstance] getMotd] && [[[SOGoAdmin sharedInstance] getMotd] length] > 1;
|
||||
}
|
||||
|
||||
- (BOOL) hasUrlCreateAccount
|
||||
{
|
||||
NSString *url;
|
||||
url = [[SOGoSystemDefaults sharedSystemDefaults] urlCreateAccount];
|
||||
return (url != nil && [url length] > 0);
|
||||
}
|
||||
|
||||
- (NSString *) urlCreateAccount
|
||||
{
|
||||
return [[SOGoSystemDefaults sharedSystemDefaults]
|
||||
urlCreateAccount];
|
||||
}
|
||||
|
||||
|
||||
@end /* SOGoRootPage */
|
||||
@@ -32,6 +32,7 @@
|
||||
|
||||
#import <SOGo/SOGoCache.h>
|
||||
#import <SOGo/SOGoCASSession.h>
|
||||
#import <SOGo/SOGoOpenIdSession.h>
|
||||
#if defined(SAML2_CONFIG)
|
||||
#import <SOGo/SOGoSAML2Session.h>
|
||||
#endif
|
||||
@@ -419,18 +420,38 @@
|
||||
|
||||
- (NSString *) _logoutRedirectURL
|
||||
{
|
||||
NSString *redirectURL;
|
||||
NSString *redirectURL, *login, *loginDomain, *authType;
|
||||
SOGoSystemDefaults *sd;
|
||||
id container;
|
||||
NSRange r;
|
||||
|
||||
|
||||
login = [[context activeUser] login];
|
||||
r = [login rangeOfString: @"@"];
|
||||
if (r.location != NSNotFound)
|
||||
loginDomain = [login substringFromIndex: r.location+1];
|
||||
else
|
||||
loginDomain = nil;
|
||||
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
if ([[sd authenticationType] isEqualToString: @"cas"])
|
||||
{
|
||||
redirectURL = [SOGoCASSession CASURLWithAction: @"logout"
|
||||
andParameters: nil];
|
||||
}
|
||||
if(loginDomain && [sd doesLoginTypeByDomain])
|
||||
authType = [sd getLoginTypeForDomain: loginDomain];
|
||||
else
|
||||
authType = [sd authenticationType];
|
||||
|
||||
if ([authType isEqualToString: @"cas"])
|
||||
{
|
||||
redirectURL = [SOGoCASSession CASURLWithAction: @"logout"
|
||||
andParameters: nil];
|
||||
}
|
||||
else if ([authType isEqualToString: @"openid"])
|
||||
{
|
||||
SOGoOpenIdSession* session;
|
||||
session = [SOGoOpenIdSession OpenIdSession: loginDomain];
|
||||
redirectURL = [session logoutUrl];
|
||||
}
|
||||
#if defined(SAML2_CONFIG)
|
||||
else if ([[sd authenticationType] isEqualToString: @"saml2"])
|
||||
else if ([authType isEqualToString: @"saml2"])
|
||||
{
|
||||
NSString *username, *password, *domain, *value;
|
||||
SOGoSAML2Session *saml2Session;
|
||||
|
||||
@@ -168,6 +168,16 @@
|
||||
pageName = "SOGoRootPage";
|
||||
actionName = "connect";
|
||||
};
|
||||
connectName = {
|
||||
protectedBy = "<public>";
|
||||
pageName = "SOGoRootPage";
|
||||
actionName = "connectName";
|
||||
};
|
||||
openid_redirect = {
|
||||
protectedBy = "<public>";
|
||||
pageName = "SOGoRootPage";
|
||||
actionName = "openIdRedirect";
|
||||
};
|
||||
changePassword = {
|
||||
protectedBy = "<public>";
|
||||
pageName = "SOGoRootPage";
|
||||
|
||||
@@ -274,11 +274,6 @@
|
||||
"Confirmation" = "Confirmation";
|
||||
"Change" = "Change";
|
||||
"Passwords don't match" = "Passwords don't match";
|
||||
"POLICY_MIN_LOWERCASE_LETTER" = "Minimum of %{0} lowercase letter";
|
||||
"POLICY_MIN_UPPERCASE_LETTER" = "Minimum of %{0} uppercase letter";
|
||||
"POLICY_MIN_DIGIT" = "Minimum of %{0} digit";
|
||||
"POLICY_MIN_SPECIAL_SYMBOLS" = "Minimum of %{0} special symbols";
|
||||
"POLICY_MIN_LENGTH" = "Minimum length of %{0} characters";
|
||||
|
||||
|
||||
/* Event+task classifications */
|
||||
|
||||
@@ -274,11 +274,7 @@
|
||||
"Confirmation" = "Confirmation";
|
||||
"Change" = "Changer";
|
||||
"Passwords don't match" = "Les mots de passe ne correspondent pas";
|
||||
"POLICY_MIN_LOWERCASE_LETTER" = "Au moins %{0} lettre(s) minuscule(s)";
|
||||
"POLICY_MIN_UPPERCASE_LETTER" = "Au moins %{0} lettre(s) majuscule(s)";
|
||||
"POLICY_MIN_DIGIT" = "Au moins %{0} chiffre(s)";
|
||||
"POLICY_MIN_SPECIAL_SYMBOLS" = "Au moins %{0} caractère(s) special(aux)";
|
||||
"POLICY_MIN_LENGTH" = "Longueur d'au moins %{0} caractère(s)";
|
||||
|
||||
|
||||
|
||||
/* Event+task classifications */
|
||||
|
||||
@@ -147,6 +147,7 @@ static SoProduct *preferencesProduct = nil;
|
||||
NSMutableDictionary *values, *account, *vacation;
|
||||
SOGoUserDefaults *defaults;
|
||||
SOGoDomainDefaults *domainDefaults;
|
||||
SOGoSystemDefaults *sd;
|
||||
NSMutableArray *accounts;
|
||||
NSDictionary *calendarCategoryLabels, *contactsCategoriesLabels, *vacationOptions;
|
||||
|
||||
@@ -333,6 +334,13 @@ static SoProduct *preferencesProduct = nil;
|
||||
[defaults setCalendarCategoriesColors: colors];
|
||||
}
|
||||
}
|
||||
// Add the jisti link if needed
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
if([sd isCalendarJitsiLinkEnabled])
|
||||
{
|
||||
if (![[defaults source] objectForKey: @"SOGoCalendarJitsiBaseUrl"])
|
||||
[[defaults source] setObject: [domainDefaults calendarJistiBaseUrl] forKey: @"SOGoCalendarJitsiBaseUrl"];
|
||||
}
|
||||
|
||||
//
|
||||
// Default Contacts preferences
|
||||
|
||||
@@ -278,7 +278,7 @@ static NSArray *reminderValues = nil;
|
||||
userSource = [user authenticationSource];
|
||||
|
||||
for(policy in [userSource userPasswordPolicy]) {
|
||||
[translations setObject:[self labelForKey:[policy objectForKey:@"label"]]
|
||||
[translations setObject:[self commonLabelForKey:[policy objectForKey:@"label"]]
|
||||
forKey: [policy objectForKey:@"label"]];
|
||||
}
|
||||
translatedUserPolicy = [SOGoPasswordPolicy createPasswordPolicyLabels: [userSource userPasswordPolicy]
|
||||
@@ -1000,12 +1000,16 @@ static NSArray *reminderValues = nil;
|
||||
//
|
||||
- (NSArray *) daysBetweenResponsesList
|
||||
{
|
||||
static NSArray *daysBetweenResponses = nil;
|
||||
static NSMutableArray *daysBetweenResponses = nil;
|
||||
|
||||
if (!daysBetweenResponses)
|
||||
{
|
||||
daysBetweenResponses = [NSArray arrayWithObjects: @"1", @"2", @"3",
|
||||
daysBetweenResponses = [NSMutableArray arrayWithObjects: @"1", @"2", @"3",
|
||||
@"5", @"7", @"14", @"21", @"30", nil];
|
||||
if ([[user domainDefaults] vacationAllowZeroDays])
|
||||
{
|
||||
[daysBetweenResponses insertObject:@"0" atIndex:0];
|
||||
}
|
||||
[daysBetweenResponses retain];
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"Save and Close" = "Save and Close";
|
||||
"Close" = "Close";
|
||||
"Invite Attendees" = "Invite Attendees";
|
||||
"Create Jitsi Meeting" = "Create Jitsi Meeting";
|
||||
"Attach" = "Attach";
|
||||
"Update" = "Update";
|
||||
"Cancel" = "Cancel";
|
||||
|
||||
@@ -117,6 +117,7 @@
|
||||
"Save and Close" = "Enregistrer et fermer";
|
||||
"Close" = "Fermer";
|
||||
"Invite Attendees" = "Participants";
|
||||
"Create Jitsi Meeting" = "Créer Une Réunion Jitsi";
|
||||
"Attach" = "Joindre";
|
||||
"Update" = "Mettre à jour";
|
||||
"Cancel" = "Annuler";
|
||||
|
||||
@@ -350,6 +350,7 @@ static NSArray *tasksFields = nil;
|
||||
//NSLog(@"***[UIxCalListingActions _fixDates:] %@ = %@ (%i)", aDateField, aDate, daylightOffset);
|
||||
if (daylightOffset)
|
||||
{
|
||||
[self logWithFormat: @"Changing date (aDate '%@'), (startDate '%@'), (daylightOffset %d)", aDate, startDate, daylightOffset];
|
||||
// If it is the end date and allday, we need to remove the offset
|
||||
if ([fields[count * 2] isEqualToString: @"endDate"] && [[theRecord objectForKey: @"c_isallday"] boolValue] && daylightOffset > 0)
|
||||
aDate = [aDate dateByAddingYears: 0 months: 0 days: 0 hours: 0
|
||||
|
||||
@@ -517,7 +517,15 @@
|
||||
@interface UIxAppointmentEditorTemplate : UIxComponentEditorTemplate
|
||||
@end
|
||||
|
||||
|
||||
@implementation UIxAppointmentEditorTemplate
|
||||
|
||||
- (BOOL) showJitsiLinkCreation
|
||||
{
|
||||
SOGoSystemDefaults *sd;
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
return [sd isCalendarJitsiLinkEnabled];
|
||||
}
|
||||
@end
|
||||
|
||||
/* Task Editor */
|
||||
|
||||
@@ -495,7 +495,7 @@
|
||||
</md-autocomplete>
|
||||
<md-chip-template>
|
||||
<div class="sg-tile-content">
|
||||
{{$chip.c_cn}}
|
||||
{{$chip.c_cn || $chip.email}}
|
||||
</div>
|
||||
</md-chip-template>
|
||||
</md-chips>
|
||||
|
||||
@@ -87,11 +87,16 @@
|
||||
<md-icon label:aria-label="Subject">{{ mailbox.sortedBy('subject') ? 'check' : null }}</md-icon> <var:string label:value="Subject"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-menu-item ng-if="!mailbox.selectedFolder.isSentFolder">
|
||||
<md-button ng-click="mailbox.sort('from')">
|
||||
<md-icon label:aria-label="From">{{ mailbox.sortedBy('from') ? 'check' : null }}</md-icon> <var:string label:value="From"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item ng-if="mailbox.selectedFolder.isSentFolder">
|
||||
<md-button ng-click="mailbox.sort('to')">
|
||||
<md-icon label:aria-label="To">{{ mailbox.sortedBy('to') ? 'check' : null }}</md-icon> <var:string label:value="To"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<md-menu-item>
|
||||
<md-button ng-click="mailbox.sort('date')">
|
||||
<md-icon label:aria-label="Date">{{ mailbox.sortedBy('date') ? 'check' : null }}</md-icon> <var:string label:value="Date"/>
|
||||
@@ -143,7 +148,8 @@
|
||||
</md-input-container>
|
||||
<md-input-container flex="25">
|
||||
<md-select label:aria-label="Search scope">
|
||||
<md-option value="subject_or_from" selected="selected"><var:string label:value="Subject or Sender"/></md-option>
|
||||
<md-option value="subject_or_from" selected="selected" ng-if="!mailbox.selectedFolder.isSentFolder"><var:string label:value="Subject or Sender"/></md-option>
|
||||
<md-option value="subject_or_to" selected="selected" ng-if="mailbox.selectedFolder.isSentFolder"><var:string label:value="Subject or Recipient"/></md-option>
|
||||
<md-option value="subject"><var:string label:value="Subject"/></md-option>
|
||||
<md-option value="from"><var:string label:value="Sender"/></md-option>
|
||||
<md-option value="to_or_cc"><var:string label:value="To or Cc"/></md-option>
|
||||
@@ -265,7 +271,7 @@
|
||||
<md-button class="sg-no-wrap"
|
||||
aria-label="{{folder.$displayName}}"
|
||||
ng-disabled="::(mailbox.service.selectedFolder.id == folder.id)"
|
||||
ng-click="mailbox.moveSelectedMessages(folder.id)">
|
||||
ng-click="mailbox.moveSelectedMessages(folder.id, null)">
|
||||
<span ng-class="::('sg-child-level-' + folder.level)"
|
||||
ng-bind="folder.$displayName"><!-- mailbox name --></span>
|
||||
</md-button>
|
||||
@@ -632,4 +638,87 @@
|
||||
</md-dialog-actions>
|
||||
</md-dialog>
|
||||
</script>
|
||||
<!-- Remove old emails -->
|
||||
<script type="text/ng-template" id="cleanMailbox">
|
||||
<md-dialog flex="50" flex-sm="80" flex-xs="100">
|
||||
<!-- Toolbar -->
|
||||
<md-toolbar>
|
||||
<div class="md-toolbar-tools">
|
||||
<md-icon class="material-icons sg-icon-toolbar-bg">delete</md-icon>
|
||||
<div class="pseudo-input-container md-flex" ng-if="::!dialogCtrl.isMailbox">
|
||||
<var:string label:value="Clean folder"/>
|
||||
</div>
|
||||
<div class="pseudo-input-container md-flex" ng-if="::dialogCtrl.isMailbox">
|
||||
<var:string label:value="Clean mailbox"/>
|
||||
</div>
|
||||
<md-button class="md-icon-button" ng-click="dialogCtrl.closeDialog()">
|
||||
<md-icon aria-label="Close dialog">close</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</md-toolbar>
|
||||
|
||||
<!-- Dialog Content -->
|
||||
<md-dialog-content class="md-dialog-content">
|
||||
<div class="md-dialog-content">
|
||||
<h4>
|
||||
{{ dialogCtrl.name }}
|
||||
</h4>
|
||||
|
||||
<md-input-container class="md-block">
|
||||
<md-checkbox ng-model="dialogCtrl.form.permanentlyDelete" label:aria-label="Delete permanently (do not use trash)">
|
||||
<var:string label:value="Delete permanently (do not use trash)"/>
|
||||
</md-checkbox>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container class="md-block">
|
||||
<md-checkbox ng-model="dialogCtrl.form.applyToSubfolders" label:aria-label="Apply to subfolders">
|
||||
<var:string label:value="Apply to subfolders"/>
|
||||
</md-checkbox>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container class="md-block">
|
||||
<label><var:string label:value="Delete e-mails older than"/></label>
|
||||
<md-select ng-model="dialogCtrl.form.filterDuration">
|
||||
<md-option value="3m"><var:string label:value="3 months"/></md-option>
|
||||
<md-option value="6m"><var:string label:value="6 months"/></md-option>
|
||||
<md-option value="9m"><var:string label:value="9 months"/></md-option>
|
||||
<md-option value="1y"><var:string label:value="1 year"/></md-option>
|
||||
<md-option value="custom"><var:string label:value="Custom"/></md-option>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container class="md-block" ng-show="['custom'].indexOf(dialogCtrl.form.filterDuration) != -1">
|
||||
<label><var:string label:value="Date"/></label>
|
||||
<md-datepicker class="md-text"
|
||||
ng-model="dialogCtrl.form.filterDurationDate"
|
||||
md-hide-icons="calendar"
|
||||
md-max-date="dialogCtrl.maxDate"
|
||||
ng-change="dialogCtrl.mainController.changeDate()">
|
||||
</md-datepicker>
|
||||
</md-input-container>
|
||||
|
||||
<md-input-container class="md-block" ng-show="dialogCtrl.isWarningDisplayed()">
|
||||
<md-checkbox ng-model="dialogCtrl.form.confirmDelete" label:aria-label="You are about to permanently delete some messages. Are you sure you want to proceed with this action?">
|
||||
<span class="clean-mailbox-warn"><var:string label:value="You are about to permanently delete some messages. Are you sure you want to proceed with this action?" /></span>
|
||||
</md-checkbox>
|
||||
</md-input-container>
|
||||
</div>
|
||||
</md-dialog-content>
|
||||
|
||||
<!-- Dialog Actions -->
|
||||
<md-dialog-actions>
|
||||
<md-button class="md-warn" ng-click="dialogCtrl.closeDialog()">
|
||||
<var:string label:value="Cancel" />
|
||||
</md-button>
|
||||
<div class="sg-progress-linear-bottom"
|
||||
ng-show="dialogCtrl.isLoading()">
|
||||
<md-progress-linear class="md-accent"
|
||||
md-mode="indeterminate"><!-- progress --></md-progress-linear>
|
||||
</div>
|
||||
<md-button class="md-primary" ng-disabled="dialogCtrl.isApplyDisabled()" ng-click="dialogCtrl.apply()">
|
||||
<var:string label:value="Apply" />
|
||||
</md-button>
|
||||
</md-dialog-actions>
|
||||
</md-dialog>
|
||||
</script>
|
||||
</container>
|
||||
@@ -69,6 +69,13 @@
|
||||
<var:string label:value="New Folder..."/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<var:if condition="isCleanMailboxEnabled">
|
||||
<md-menu-item>
|
||||
<md-button type="button" ng-click="app.showCleanMailboxPanel(null, account)">
|
||||
<var:string label:value="Clean mailbox"/>...
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
</var:if>
|
||||
<md-menu-item ng-show="::account.id == 0">
|
||||
<md-button
|
||||
label:aria-label="Search"
|
||||
@@ -179,6 +186,13 @@
|
||||
<var:string label:value="Compact"/>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
<var:if condition="isCleanMailboxEnabled">
|
||||
<md-menu-item ng-show="::($menuCtrl.folder.type != 'trash')">
|
||||
<md-button type="button" ng-click="$menuCtrl.cleanMailbox()">
|
||||
<var:string label:value="Clean folder"/>...
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
</var:if>
|
||||
<md-menu-item ng-show="::$menuCtrl.folder.$isEditable">
|
||||
<md-button type="button" ng-click="$menuCtrl.confirmDelete()">
|
||||
<var:string label:value="Delete"/>
|
||||
|
||||
@@ -75,6 +75,53 @@
|
||||
<md-tooltip md-direction="bottom"><var:string label:value="Delete selected message or folder"/></md-tooltip>
|
||||
<md-icon>delete</md-icon>
|
||||
</md-button>
|
||||
|
||||
<md-menu>
|
||||
<md-button class="sg-icon-button" label:aria-label="Move To"
|
||||
ng-click="$mdMenu.open($event)">
|
||||
<md-tooltip md-direction="bottom"><var:string label:value="Move To"/></md-tooltip>
|
||||
<md-icon>call_split</md-icon>
|
||||
</md-button>
|
||||
<md-menu-content width="4">
|
||||
<md-menu-item>
|
||||
<md-menu>
|
||||
<md-button label:aria-label="Move To" ng-click="$mdMenu.open($event)">
|
||||
<var:string label:value="Move To"/>
|
||||
</md-button>
|
||||
<md-menu-content width="4">
|
||||
<div ng-repeat="account in ::mailbox.accounts track by account.id">
|
||||
<md-menu-item>
|
||||
<md-menu>
|
||||
<md-button class="md-primary"
|
||||
aria-label="{{ ::account.name }}"
|
||||
ng-click="$mdMenu.open($event)"
|
||||
ng-bind="::account.name"><!-- account name --></md-button>
|
||||
<md-menu-content class="md-dense" width="4">
|
||||
<div ng-repeat="folder in
|
||||
account.$flattenMailboxes({ all: true })
|
||||
track by folder.path">
|
||||
<md-menu-item>
|
||||
<md-button class="sg-no-wrap"
|
||||
aria-label="{{folder.$displayName}}"
|
||||
ng-disabled="::(mailbox.service.selectedFolder.id == folder.id)"
|
||||
ng-click="mailbox.moveSelectedMessages(folder.id, viewer.message)">
|
||||
<span ng-class="::('sg-child-level-' + folder.level)"
|
||||
ng-bind="folder.$displayName"><!-- mailbox name --></span>
|
||||
</md-button>
|
||||
</md-menu-item>
|
||||
</div>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
</md-menu-item>
|
||||
</div>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
</md-menu-item>
|
||||
</md-menu-content>
|
||||
</md-menu>
|
||||
|
||||
|
||||
|
||||
<md-button class="sg-icon-button hide show-gt-md" label:aria-label="'Open in New Mail Window'.asSafeJSString"
|
||||
ng-hide="::isPopup"
|
||||
ng-click="viewer.openInPopup()">
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
<script type="text/javascript">
|
||||
var cookieUsername = <var:string var:value="cookieUsername.doubleQuotedString" const:escapeHTML="NO"/>;
|
||||
var language = '<var:string var:value="language" const:escapeHTML="NO"/>';
|
||||
var loginHint = '<var:string var:value="getLoginHint" const:escapeHTML="NO"/>'
|
||||
</script>
|
||||
|
||||
<!--
|
||||
@@ -44,111 +45,121 @@
|
||||
</div>
|
||||
</var:if>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="sg-login md-default-theme md-bg md-accent" flex-gt-md="50">
|
||||
<div id="login" class="sg-login-content md-padding">
|
||||
<form name="loginForm" layout="column"
|
||||
ng-cloak="ng-cloak"
|
||||
ng-submit="app.login()">
|
||||
<var:if condition="hasLoginSuffix">
|
||||
<input type="hidden" ng-model="app.creds.loginSuffix" var:value="loginSuffix"/>
|
||||
</var:if>
|
||||
<div ng-if="!app.loginState">
|
||||
<md-input-container class="md-block">
|
||||
<label><var:string label:value="Username"/></label>
|
||||
<md-icon>person</md-icon>
|
||||
<input autocorrect="off" autocapitalize="off" type="text" ng-model="app.creds.username" ng-required="true" ng-change="app.usernameChanged()" ng-blur="app.retrievePasswordRecoveryEnabled()" />
|
||||
</md-input-container>
|
||||
<md-input-container class="md-block">
|
||||
<label><var:string label:value="Password"/></label>
|
||||
<md-icon>vpn_key</md-icon>
|
||||
<input id="passwordField" type="password" ng-model="app.creds.password" ng-required="true"/>
|
||||
<md-icon id="password-visibility-icon" ng-click="app.changePasswordVisibility()">visibility</md-icon>
|
||||
</md-input-container>
|
||||
|
||||
|
||||
<!-- LANGUAGES SELECT -->
|
||||
<div layout="row" layout-align="start end">
|
||||
<md-icon>language</md-icon>
|
||||
<md-input-container class="md-flex">
|
||||
<label><var:string label:value="choose"/></label>
|
||||
<md-select ng-model="app.creds.language"
|
||||
var:placeholder="localizedLanguage"
|
||||
ng-change="app.changeLanguage($event)">
|
||||
<var:foreach list="languages" item="item">
|
||||
<md-option var:value="item">
|
||||
<var:string value="languageText"/>
|
||||
</md-option>
|
||||
</var:foreach>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
<!-- DOMAINS SELECT -->
|
||||
<var:if condition="hasLoginDomains">
|
||||
<div layout="row" layout-align="start end">
|
||||
<md-icon>domain</md-icon>
|
||||
<md-input-container class="md-flex">
|
||||
<md-select class="md-flex" ng-model="app.creds.domain" label:placeholder="choose" ng-change="app.retrievePasswordRecoveryEnabled()">
|
||||
<var:foreach list="loginDomains" item="item">
|
||||
<md-option var:value="item">
|
||||
<var:string value="item"/>
|
||||
</md-option>
|
||||
</var:foreach>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</div>
|
||||
<var:if condition="doFullLogin">
|
||||
<div id="login" class="sg-login-content md-padding">
|
||||
<form name="loginForm" layout="column"
|
||||
ng-cloak="ng-cloak"
|
||||
ng-submit="app.login()">
|
||||
<var:if condition="hasLoginSuffix">
|
||||
<input type="hidden" ng-model="app.creds.loginSuffix" var:value="loginSuffix"/>
|
||||
</var:if>
|
||||
|
||||
<div layout="row" layout-align="center center">
|
||||
<md-switch class="md-accent md-hue-2"
|
||||
ng-model="app.creds.rememberLogin"
|
||||
label:arial-label="Remember username">
|
||||
<var:string label:value="Remember username"/>
|
||||
</md-switch>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="!app.loginState">
|
||||
|
||||
<!-- Password recovery -->
|
||||
<div layout="row" layout-align="center center" ng-if="app.passwordRecovery.passwordRecoveryEnabled">
|
||||
<div ng-if="app.showLogin">
|
||||
<a href="#" ng-click="app.passwordRecoveryInfo()" sg-ripple-click="loginContent" class="password-lost-link"><var:string label:value="Password lost"/></a>
|
||||
</div>
|
||||
</div>
|
||||
<md-input-container class="md-block">
|
||||
<label><var:string label:value="Username"/></label>
|
||||
<md-icon>person</md-icon>
|
||||
<input autocorrect="off" autocapitalize="off" type="text" ng-model="app.creds.username" ng-required="true" ng-change="app.usernameChanged()" ng-blur="app.retrievePasswordRecoveryEnabled()" />
|
||||
</md-input-container>
|
||||
|
||||
<!-- CONNECT BUTTON -->
|
||||
<div layout="row" layout-align="space-between center" ng-if="!app.loginState">
|
||||
<md-button class="md-icon-button"
|
||||
label:aria-label="About"
|
||||
ng-click="app.showAbout()">
|
||||
<md-icon>info</md-icon>
|
||||
</md-button>
|
||||
<div>
|
||||
<md-button class="md-fab md-accent md-hue-2" type="submit"
|
||||
label:aria-label="Connect"
|
||||
ng-if="!app.loginState"
|
||||
ng-disabled="loginForm.$invalid"
|
||||
sg-ripple-click="loginContent">
|
||||
<md-icon>arrow_forward</md-icon>
|
||||
<md-input-container class="md-block">
|
||||
<label><var:string label:value="Password"/></label>
|
||||
<md-icon>vpn_key</md-icon>
|
||||
<input id="passwordField" type="password" ng-model="app.creds.password" ng-required="true"/>
|
||||
<md-icon id="password-visibility-icon" ng-click="app.changePasswordVisibility()">visibility</md-icon>
|
||||
</md-input-container>
|
||||
|
||||
|
||||
<!-- LANGUAGES SELECT -->
|
||||
<div layout="row" layout-align="start end">
|
||||
<md-icon>language</md-icon>
|
||||
<md-input-container class="md-flex">
|
||||
<label><var:string label:value="choose"/></label>
|
||||
<md-select ng-model="app.creds.language"
|
||||
var:placeholder="localizedLanguage"
|
||||
ng-change="app.changeLanguage($event)">
|
||||
<var:foreach list="languages" item="item">
|
||||
<md-option var:value="item">
|
||||
<var:string value="languageText"/>
|
||||
</md-option>
|
||||
</var:foreach>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
<!-- DOMAINS SELECT -->
|
||||
<var:if condition="hasLoginDomains">
|
||||
<div layout="row" layout-align="start end">
|
||||
<md-icon>domain</md-icon>
|
||||
<md-input-container class="md-flex">
|
||||
<md-select class="md-flex" ng-model="app.creds.domain" label:placeholder="choose" ng-change="app.retrievePasswordRecoveryEnabled()">
|
||||
<var:foreach list="loginDomains" item="item">
|
||||
<md-option var:value="item">
|
||||
<var:string value="item"/>
|
||||
</md-option>
|
||||
</var:foreach>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</div>
|
||||
</var:if>
|
||||
|
||||
<div layout="row" layout-align="center center">
|
||||
<md-switch class="md-accent md-hue-2"
|
||||
ng-model="app.creds.rememberLogin"
|
||||
label:arial-label="Remember username">
|
||||
<var:string label:value="Remember username"/>
|
||||
</md-switch>
|
||||
</div>
|
||||
<var:if condition="hasUrlCreateAccount">
|
||||
<div layout="row" layout-align="center center">
|
||||
<a var:href="urlCreateAccount" target="_blank" class="create-account-link"><var:string label:value="Create an account"/></a>
|
||||
</div>
|
||||
</var:if>
|
||||
</div>
|
||||
|
||||
<!-- Password recovery -->
|
||||
<div layout="row" layout-align="center center" ng-if="app.passwordRecovery.passwordRecoveryEnabled">
|
||||
<div ng-if="app.showLogin">
|
||||
<a href="#" ng-click="app.passwordRecoveryInfo()" sg-ripple-click="loginContent" class="password-lost-link"><var:string label:value="Password lost"/></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CONNECT BUTTON -->
|
||||
<div layout="row" layout-align="space-between center" ng-if="!app.loginState">
|
||||
<md-button class="md-icon-button"
|
||||
label:aria-label="About"
|
||||
ng-click="app.showAbout()">
|
||||
<md-icon>info</md-icon>
|
||||
</md-button>
|
||||
<div>
|
||||
<md-button class="md-fab md-accent md-hue-2" type="submit"
|
||||
label:aria-label="Connect"
|
||||
ng-if="!app.loginState"
|
||||
ng-disabled="loginForm.$invalid"
|
||||
sg-ripple-click="loginContent">
|
||||
<md-icon>arrow_forward</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<sg-ripple class="md-default-theme md-accent md-bg"
|
||||
ng-class="{ 'md-warn': app.loginState == 'error' }"><!-- ripple background --></sg-ripple>
|
||||
<sg-ripple-content class="md-flex ng-hide"
|
||||
layout="column" layout-align="center center" layout-fill="layout-fill"
|
||||
ng-switch="app.loginState">
|
||||
<sg-ripple class="md-default-theme md-accent md-bg"
|
||||
ng-class="{ 'md-warn': app.loginState == 'error' }"><!-- ripple background --></sg-ripple>
|
||||
<sg-ripple-content class="md-flex ng-hide"
|
||||
layout="column" layout-align="center center" layout-fill="layout-fill"
|
||||
ng-switch="app.loginState">
|
||||
|
||||
<!-- Authenticating -->
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="authenticating">
|
||||
<md-progress-circular class="md-hue-1"
|
||||
md-mode="indeterminate"
|
||||
md-diameter="32"><!-- mailbox loading progress --></md-progress-circular>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
<var:string label:value="Authenticating"/>
|
||||
<!-- Authenticating -->
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="authenticating">
|
||||
<md-progress-circular class="md-hue-1"
|
||||
md-mode="indeterminate"
|
||||
md-diameter="32"><!-- mailbox loading progress --></md-progress-circular>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
<var:string label:value="Authenticating"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<var:if condition="isTotpEnabled">
|
||||
<!-- TOTP Code -->
|
||||
@@ -158,7 +169,7 @@
|
||||
<md-input-container class="md-block">
|
||||
<label><var:string label:value="Verification Code"/></label>
|
||||
<md-icon>lock</md-icon>
|
||||
<input type="text"
|
||||
<input type="text" inputmode="numeric" autocomplete="off"
|
||||
ng-pattern="app.verificationCodePattern"
|
||||
ng-model="app.creds.verificationCode"
|
||||
ng-required="app.loginState == 'totpcode'"
|
||||
@@ -208,17 +219,18 @@
|
||||
<!-- Password policy: Password is expired / password recovery-->
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="passwordchange">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large" ng-if="!app.isInPasswordRecoveryMode()">watch_later</md-icon>
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large" ng-if="app.isInPasswordRecoveryMode()">vpn_key</md-icon>
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large" ng-if="!(app.isInPasswordRecoveryMode() || app.isPasswordExpiredSecurity())">watch_later</md-icon>
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large" ng-if="app.isInPasswordRecoveryMode() || app.isPasswordExpiredSecurity()">vpn_key</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="!app.isInPasswordRecoveryMode()">
|
||||
<var:string label:value="Your password has expired, please enter a new one below"/>
|
||||
<span ng-if="!app.isPasswordExpiredSecurity()"><var:string label:value="Your password has expired, please enter a new one below"/></span>
|
||||
<span ng-if="app.isPasswordExpiredSecurity()"><var:string label:value="Your password is too weak. Please choose a stronger password to enhance your security"/></span>
|
||||
</div>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="app.isInPasswordRecoveryMode()">
|
||||
<var:string label:value="Please enter a new password below"/>
|
||||
</div>
|
||||
<div flex="100">
|
||||
<div layout="row" layout-xs="column">
|
||||
<md-input-container class="md-block" flex="flex" ng-if="!app.isInPasswordRecoveryMode()">
|
||||
<md-input-container class="md-block" flex="flex" ng-if="!(app.isInPasswordRecoveryMode())">
|
||||
<label><var:string label:value="Current password"/>
|
||||
</label>
|
||||
<input type="password" sg-no-dirty-check="true" ng-model="app.passwords.oldPassword"/>
|
||||
@@ -227,6 +239,13 @@
|
||||
<label><var:string label:value="New password"/>
|
||||
</label>
|
||||
<input type="password" sg-no-dirty-check="true" ng-model="app.passwords.newPassword"/>
|
||||
<div class="sg-hint">
|
||||
<ul class="sg-padded">
|
||||
<li ng-repeat="item in app.passwordPolicy">
|
||||
{{ item.label }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</md-input-container>
|
||||
<md-input-container class="md-block" flex="flex">
|
||||
<label><var:string label:value="Confirmation"/>
|
||||
@@ -245,126 +264,235 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password policy: Grace period -->
|
||||
<div layout="row" layout-align="center center" layout-fill="layout-fill"
|
||||
ng-switch-when="passwordwillexpire">
|
||||
<div layout="column" layout-align="center center" flex-xs="flex-xs" flex-gt-xs="50">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">warning</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="app.cn">
|
||||
<var:string label:value="Welcome"/> {{app.cn}}
|
||||
</div>
|
||||
<div class="md-padding" layout="row" layout-align="start center">
|
||||
<md-icon>priority_high</md-icon>
|
||||
<div class="md-padding">{{app.errorMessage}}</div>
|
||||
</div>
|
||||
<div layout="row" layout-align="end center">
|
||||
<md-button
|
||||
ng-click="app.loginState = 'passwordexpired'"><var:string label:value="Change your Password"/></md-button>
|
||||
<md-button
|
||||
ng-click="app.continueLogin()"
|
||||
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password recovery -->
|
||||
<var:if condition="hasPasswordRecovery">
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="passwordrecovery">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">vpn_key</md-icon>
|
||||
<div flex="100">
|
||||
<div layout="row" layout-xs="column" class="md-padding" layout-align="center center">
|
||||
<div ng-if="app.passwordRecovery.showLoader">
|
||||
<md-progress-circular class="md-hue-1"
|
||||
md-mode="indeterminate"
|
||||
md-diameter="32"><!-- password recovery progress --></md-progress-circular>
|
||||
</div>
|
||||
<div ng-if="'SecretQuestion' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<div ng-if="!app.passwordRecovery.showLoader">
|
||||
{{ app.passwordRecovery.passwordRecoveryQuestion }}
|
||||
<md-input-container class="md-block">
|
||||
<label><var:string label:value="Answer"/></label>
|
||||
<input autocorrect="off" autocapitalize="off" type="text" ng-model="app.passwordRecovery.passwordRecoveryQuestionAnswer" />
|
||||
</md-input-container>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="'SecondaryEmail' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<div ng-if="!app.passwordRecovery.showLoader">
|
||||
{{ app.passwordRecovery.passwordRecoverySecondaryEmailText }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- Password policy: Grace period -->
|
||||
<div layout="row" layout-align="center center" layout-fill="layout-fill"
|
||||
ng-switch-when="passwordwillexpire">
|
||||
<div layout="column" layout-align="center center" flex-xs="flex-xs" flex-gt-xs="50">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">warning</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding" ng-if="app.cn">
|
||||
<var:string label:value="Welcome"/> {{app.cn}}
|
||||
</div>
|
||||
<div layout="row" layout-align="end center" ng-if="!app.passwordRecovery.showLoader">
|
||||
<md-button ng-click="app.passwordRecoveryAbort()" type="button" >
|
||||
<var:string label:value="Back"/>
|
||||
</md-button>
|
||||
<div ng-if="'SecretQuestion' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<md-button ng-click="app.passwordRecoveryCheck()" type="button" >
|
||||
<var:string label:value="Next"/>
|
||||
</md-button>
|
||||
</div>
|
||||
<div ng-if="'SecondaryEmail' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<md-button ng-click="app.passwordRecoveryEmail()" type="button" >
|
||||
<var:string label:value="Next"/>
|
||||
</md-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="sendrecoverymail">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">local_shipping</md-icon>
|
||||
<div flex="100">
|
||||
<div layout="row" layout-xs="column" class="md-padding">
|
||||
<div ng-if="'SecondaryEmail' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<var:string label:value="A password reset link has been sent, please check your recovery e-mail mailbox and click on the link"/>
|
||||
</div>
|
||||
<div class="md-padding" layout="row" layout-align="start center">
|
||||
<md-icon>priority_high</md-icon>
|
||||
<div class="md-padding">{{app.errorMessage}}</div>
|
||||
</div>
|
||||
<div layout="row" layout-align="end center">
|
||||
<md-button ng-click="app.passwordRecoveryAbort()" type="button" >
|
||||
<var:string label:value="Back"/>
|
||||
</md-button>
|
||||
<md-button
|
||||
ng-click="app.loginState = 'passwordexpired'"><var:string label:value="Change your Password"/></md-button>
|
||||
<md-button
|
||||
ng-click="app.continueLogin()"
|
||||
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</var:if>
|
||||
|
||||
<!-- Logged in -->
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="logged">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">done</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
<var:string label:value="Welcome"/> {{app.cn}}
|
||||
<!-- Password recovery -->
|
||||
<var:if condition="hasPasswordRecovery">
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="passwordrecovery">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">vpn_key</md-icon>
|
||||
<div flex="100">
|
||||
<div layout="row" layout-xs="column" class="md-padding" layout-align="center center">
|
||||
<div ng-if="app.passwordRecovery.showLoader">
|
||||
<md-progress-circular class="md-hue-1"
|
||||
md-mode="indeterminate"
|
||||
md-diameter="32"><!-- password recovery progress --></md-progress-circular>
|
||||
</div>
|
||||
<div ng-if="'SecretQuestion' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<div ng-if="!app.passwordRecovery.showLoader">
|
||||
{{ app.passwordRecovery.passwordRecoveryQuestion }}
|
||||
<md-input-container class="md-block">
|
||||
<label><var:string label:value="Answer"/></label>
|
||||
<input autocorrect="off" autocapitalize="off" type="text" ng-model="app.passwordRecovery.passwordRecoveryQuestionAnswer" />
|
||||
</md-input-container>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="'SecondaryEmail' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<div ng-if="!app.passwordRecovery.showLoader">
|
||||
{{ app.passwordRecovery.passwordRecoverySecondaryEmailText }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div layout="row" layout-align="end center" ng-if="!app.passwordRecovery.showLoader">
|
||||
<md-button ng-click="app.passwordRecoveryAbort()" type="button" >
|
||||
<var:string label:value="Back"/>
|
||||
</md-button>
|
||||
<div ng-if="'SecretQuestion' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<md-button ng-click="app.passwordRecoveryCheck()" type="button" >
|
||||
<var:string label:value="Next"/>
|
||||
</md-button>
|
||||
</div>
|
||||
<div ng-if="'SecondaryEmail' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<md-button ng-click="app.passwordRecoveryEmail()" type="button" >
|
||||
<var:string label:value="Next"/>
|
||||
</md-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="sendrecoverymail">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">local_shipping</md-icon>
|
||||
<div flex="100">
|
||||
<div layout="row" layout-xs="column" class="md-padding">
|
||||
<div ng-if="'SecondaryEmail' === app.passwordRecovery.passwordRecoveryMode">
|
||||
<var:string label:value="A password reset link has been sent, please check your recovery e-mail mailbox and click on the link"/>
|
||||
</div>
|
||||
</div>
|
||||
<div layout="row" layout-align="end center">
|
||||
<md-button ng-click="app.passwordRecoveryAbort()" type="button" >
|
||||
<var:string label:value="Back"/>
|
||||
</md-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</var:if>
|
||||
|
||||
<!-- Logged in -->
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="logged">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">done</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
<var:string label:value="Welcome"/> {{app.cn}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="message">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">done</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
{{app.errorMessage}}
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="message">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">done</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
{{app.errorMessage}}
|
||||
</div>
|
||||
<md-button
|
||||
ng-click="app.continueLogin()"
|
||||
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
|
||||
</div>
|
||||
<md-button
|
||||
ng-click="app.continueLogin()"
|
||||
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
|
||||
</div>
|
||||
|
||||
<!-- Error -->
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="error">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">error</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
{{app.errorMessage}}
|
||||
<!-- Error -->
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="error">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">error</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
{{app.errorMessage}}
|
||||
</div>
|
||||
<md-button
|
||||
ng-click="app.restoreLogin()"
|
||||
sg-ripple-click="loginContent"><var:string label:value="Retry"/></md-button>
|
||||
</div>
|
||||
<md-button
|
||||
ng-click="app.restoreLogin()"
|
||||
sg-ripple-click="loginContent"><var:string label:value="Retry"/></md-button>
|
||||
</div>
|
||||
|
||||
</sg-ripple-content>
|
||||
</form>
|
||||
</sg-ripple-content>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</var:if>
|
||||
<var:if condition="doPartialLogin">
|
||||
<div id="login" class="sg-login-content md-padding">
|
||||
<form name="loginForm" layout="column"
|
||||
ng-cloak="ng-cloak"
|
||||
ng-submit="app.loginName()">
|
||||
<div ng-if="!app.loginState">
|
||||
|
||||
<md-input-container class="md-block">
|
||||
<label><var:string label:value="Username"/></label>
|
||||
<md-icon>person</md-icon>
|
||||
<input autocorrect="off" autocapitalize="off" type="text" ng-model="app.creds.username" ng-required="true" ng-change="app.usernameChanged()" ng-blur="app.retrievePasswordRecoveryEnabled()" />
|
||||
</md-input-container>
|
||||
|
||||
<!-- LANGUAGES SELECT -->
|
||||
<div layout="row" layout-align="start end">
|
||||
<md-icon>language</md-icon>
|
||||
<md-input-container class="md-flex">
|
||||
<label><var:string label:value="choose"/></label>
|
||||
<md-select ng-model="app.creds.language"
|
||||
var:placeholder="localizedLanguage"
|
||||
ng-change="app.changeLanguage($event)">
|
||||
<var:foreach list="languages" item="item">
|
||||
<md-option var:value="item">
|
||||
<var:string value="languageText"/>
|
||||
</md-option>
|
||||
</var:foreach>
|
||||
</md-select>
|
||||
</md-input-container>
|
||||
</div>
|
||||
|
||||
<var:if condition="hasUrlCreateAccount">
|
||||
<div layout="row" layout-align="center center">
|
||||
<a var:href="urlCreateAccount" target="_blank" class="create-account-link"><var:string label:value="Create an account"/></a>
|
||||
</div>
|
||||
</var:if>
|
||||
</div>
|
||||
|
||||
<!-- CONNECT BUTTON -->
|
||||
<div layout="row" layout-align="space-between center" ng-if="!app.loginState">
|
||||
<md-button class="md-icon-button"
|
||||
label:aria-label="About"
|
||||
ng-click="app.showAbout()">
|
||||
<md-icon>info</md-icon>
|
||||
</md-button>
|
||||
<div>
|
||||
<md-button class="md-fab md-accent md-hue-2" type="submit"
|
||||
label:aria-label="Connect"
|
||||
ng-if="!app.loginState"
|
||||
ng-disabled="loginForm.$invalid"
|
||||
sg-ripple-click="loginContent">
|
||||
<md-icon>arrow_forward</md-icon>
|
||||
</md-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<sg-ripple class="md-default-theme md-accent md-bg"
|
||||
ng-class="{ 'md-warn': app.loginState == 'error' }"><!-- ripple background --></sg-ripple>
|
||||
<sg-ripple-content class="md-flex ng-hide"
|
||||
layout="column" layout-align="center center" layout-fill="layout-fill"
|
||||
ng-switch="app.loginState">
|
||||
|
||||
<!-- Authenticating -->
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="authenticating">
|
||||
<md-progress-circular class="md-hue-1"
|
||||
md-mode="indeterminate"
|
||||
md-diameter="32"><!-- mailbox loading progress --></md-progress-circular>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
<var:string label:value="Authenticating"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Logged in -->
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="logged">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">done</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
<var:string label:value="Welcome"/> {{app.cn}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="message">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">done</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
{{app.errorMessage}}
|
||||
</div>
|
||||
<md-button
|
||||
ng-click="app.continueLogin()"
|
||||
sg-ripple-click="loginContent"><var:string label:value="Continue"/></md-button>
|
||||
</div>
|
||||
|
||||
<!-- Error -->
|
||||
<div layout="column" layout-align="center center"
|
||||
ng-switch-when="error">
|
||||
<md-icon class="md-accent md-hue-1 sg-icon--large">error</md-icon>
|
||||
<div class="md-default-theme md-accent md-hue-1 md-fg md-padding">
|
||||
{{app.errorMessage}}
|
||||
</div>
|
||||
<md-button
|
||||
ng-click="app.restoreLogin()"
|
||||
sg-ripple-click="loginContent"><var:string label:value="Retry"/></md-button>
|
||||
</div>
|
||||
|
||||
</sg-ripple-content>
|
||||
</form>
|
||||
</div>
|
||||
</var:if>
|
||||
</div>
|
||||
</div>
|
||||
</md-content>
|
||||
|
||||
@@ -1175,7 +1175,12 @@
|
||||
ng-model="app.preferences.defaults.Vacation.daysBetweenResponse">
|
||||
<var:foreach list="daysBetweenResponsesList" item="item">
|
||||
<md-option var:value="item">
|
||||
<var:string value="item"/>
|
||||
<var:if condition="item" const:value="0">
|
||||
<i><var:string label:value="Always send vacation message response"/></i>
|
||||
</var:if>
|
||||
<var:if condition="item" const:value="0" const:negate="YES">
|
||||
<var:string value="item"/>
|
||||
</var:if>
|
||||
</md-option>
|
||||
</var:foreach>
|
||||
</md-select>
|
||||
@@ -1499,4 +1504,4 @@
|
||||
</script>
|
||||
|
||||
|
||||
</var:component>
|
||||
</var:component>
|
||||
@@ -120,6 +120,19 @@
|
||||
label:aria-label="Send Appointment Notifications">
|
||||
<var:string label:value="Send Appointment Notifications"/>
|
||||
</md-checkbox>
|
||||
<!-- create jitsi link-->
|
||||
<var:if condition="showJitsiLinkCreation">
|
||||
<div layout="row" ng-if="!editor.component.hasJitsiUrl()" layout-align="start center">
|
||||
<md-button class="md-icon-button" type="button" ng-click="editor.addJitsiUrl($event)">
|
||||
<md-icon>add_circle</md-icon>
|
||||
</md-button>
|
||||
<label class="button-label">
|
||||
<var:string label:value="Create Jitsi meeting"/>
|
||||
</label>
|
||||
</div>
|
||||
</var:if>
|
||||
|
||||
|
||||
<!-- attach urls -->
|
||||
<div ng-repeat="attach in editor.component.attachUrls">
|
||||
<div layout="row" layout-align="start center">
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
@charset "UTF-8";.ng-cloak,.x-ng-cloak,[data-ng-cloak],[ng-cloak],[ng\:cloak],[x-ng-cloak]{display:none!important}.sg-hide{opacity:0}@-moz-document url-prefix(){[layout-fill]{margin:0;width:100%;min-height:100%;height:100%}}[flex-order]{-webkit-box-ordinal-group:1;-webkit-order:0;order:0}[flex-order="-20"]{-webkit-box-ordinal-group:-19;-webkit-order:-20;order:-20}[flex-order="-19"]{-webkit-box-ordinal-group:-18;-webkit-order:-19;order:-19}[flex-order="-18"]{-webkit-box-ordinal-group:-17;-webkit-order:-18;order:-18}[flex-order="-17"]{-webkit-box-ordinal-group:-16;-webkit-order:-17;order:-17}[flex-order="-16"]{-webkit-box-ordinal-group:-15;-webkit-order:-16;order:-16}[flex-order="-15"]{-webkit-box-ordinal-group:-14;-webkit-order:-15;order:-15}[flex-order="-14"]{-webkit-box-ordinal-group:-13;-webkit-order:-14;order:-14}[flex-order="-13"]{-webkit-box-ordinal-group:-12;-webkit-order:-13;order:-13}[flex-order="-12"]{-webkit-box-ordinal-group:-11;-webkit-order:-12;order:-12}[flex-order="-11"]{-webkit-box-ordinal-group:-10;-webkit-order:-11;order:-11}[flex-order="-10"]{-webkit-box-ordinal-group:-9;-webkit-order:-10;order:-10}[flex-order="-9"]{-webkit-box-ordinal-group:-8;-webkit-order:-9;order:-9}[flex-order="-8"]{-webkit-box-ordinal-group:-7;-webkit-order:-8;order:-8}[flex-order="-7"]{-webkit-box-ordinal-group:-6;-webkit-order:-7;order:-7}[flex-order="-6"]{-webkit-box-ordinal-group:-5;-webkit-order:-6;order:-6}[flex-order="-5"]{-webkit-box-ordinal-group:-4;-webkit-order:-5;order:-5}[flex-order="-4"]{-webkit-box-ordinal-group:-3;-webkit-order:-4;order:-4}[flex-order="-3"]{-webkit-box-ordinal-group:-2;-webkit-order:-3;order:-3}[flex-order="-2"]{-webkit-box-ordinal-group:-1;-webkit-order:-2;order:-2}[flex-order="-1"]{-webkit-box-ordinal-group:0;-webkit-order:-1;order:-1}[flex-order="0"]{-webkit-box-ordinal-group:1;-webkit-order:0;order:0}[flex-order="1"]{-webkit-box-ordinal-group:2;-webkit-order:1;order:1}[flex-order="2"]{-webkit-box-ordinal-group:3;-webkit-order:2;order:2}[flex-order="3"]{-webkit-box-ordinal-group:4;-webkit-order:3;order:3}[flex-order="4"]{-webkit-box-ordinal-group:5;-webkit-order:4;order:4}[flex-order="5"]{-webkit-box-ordinal-group:6;-webkit-order:5;order:5}[flex-order="6"]{-webkit-box-ordinal-group:7;-webkit-order:6;order:6}[flex-order="7"]{-webkit-box-ordinal-group:8;-webkit-order:7;order:7}[flex-order="8"]{-webkit-box-ordinal-group:9;-webkit-order:8;order:8}[flex-order="9"]{-webkit-box-ordinal-group:10;-webkit-order:9;order:9}[flex-order="10"]{-webkit-box-ordinal-group:11;-webkit-order:10;order:10}[flex-order="11"]{-webkit-box-ordinal-group:12;-webkit-order:11;order:11}[flex-order="12"]{-webkit-box-ordinal-group:13;-webkit-order:12;order:12}[flex-order="13"]{-webkit-box-ordinal-group:14;-webkit-order:13;order:13}[flex-order="14"]{-webkit-box-ordinal-group:15;-webkit-order:14;order:14}[flex-order="15"]{-webkit-box-ordinal-group:16;-webkit-order:15;order:15}[flex-order="16"]{-webkit-box-ordinal-group:17;-webkit-order:16;order:16}[flex-order="17"]{-webkit-box-ordinal-group:18;-webkit-order:17;order:17}[flex-order="18"]{-webkit-box-ordinal-group:19;-webkit-order:18;order:18}[flex-order="19"]{-webkit-box-ordinal-group:20;-webkit-order:19;order:19}[flex-order="20"]{-webkit-box-ordinal-group:21;-webkit-order:20;order:20}[flex-offset="0"]{margin-left:0}[flex-offset="5"]{margin-left:5%}[flex-offset="10"]{margin-left:10%}[flex-offset="15"]{margin-left:15%}[flex-offset="20"]{margin-left:20%}[flex-offset="25"]{margin-left:25%}[flex-offset="30"]{margin-left:30%}[flex-offset="35"]{margin-left:35%}[flex-offset="40"]{margin-left:40%}[flex-offset="45"]{margin-left:45%}[flex-offset="50"]{margin-left:50%}[flex-offset="55"]{margin-left:55%}[flex-offset="60"]{margin-left:60%}[flex-offset="65"]{margin-left:65%}[flex-offset="70"]{margin-left:70%}[flex-offset="75"]{margin-left:75%}[flex-offset="80"]{margin-left:80%}[flex-offset="85"]{margin-left:85%}[flex-offset="90"]{margin-left:90%}[flex-offset="95"]{margin-left:95%}[flex-offset="33"]{margin-left:33.3333333333%}[flex-offset="66"]{margin-left:66.6666666667%}[layout-align="start stretch"],[layout-align]{-webkit-box-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start;-webkit-align-content:stretch;align-content:stretch;-webkit-box-align:stretch;-webkit-align-items:stretch;align-items:stretch}[layout-align="start center"],[layout-align="start end"],[layout-align="start start"],[layout-align="start stretch"],[layout-align=start]{-webkit-box-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start}[layout-align="center center"],[layout-align="center end"],[layout-align="center start"],[layout-align="center stretch"],[layout-align=center]{-webkit-box-pack:center;-webkit-justify-content:center;justify-content:center}[layout-align="end center"],[layout-align="end end"],[layout-align="end start"],[layout-align="end stretch"],[layout-align=end]{-webkit-box-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end}[layout-align="spacLine truncated
|
||||
@charset "UTF-8";.ng-cloak,.x-ng-cloak,[data-ng-cloak],[ng-cloak],[ng\:cloak],[x-ng-cloak]{display:none!important}.sg-hide{opacity:0}@-moz-document url-prefix(){[layout-fill]{margin:0;width:100%;min-height:100%;height:100%}}[flex-order]{-webkit-box-ordinal-group:1;-webkit-order:0;order:0}[flex-order="-20"]{-webkit-box-ordinal-group:-19;-webkit-order:-20;order:-20}[flex-order="-19"]{-webkit-box-ordinal-group:-18;-webkit-order:-19;order:-19}[flex-order="-18"]{-webkit-box-ordinal-group:-17;-webkit-order:-18;order:-18}[flex-order="-17"]{-webkit-box-ordinal-group:-16;-webkit-order:-17;order:-17}[flex-order="-16"]{-webkit-box-ordinal-group:-15;-webkit-order:-16;order:-16}[flex-order="-15"]{-webkit-box-ordinal-group:-14;-webkit-order:-15;order:-15}[flex-order="-14"]{-webkit-box-ordinal-group:-13;-webkit-order:-14;order:-14}[flex-order="-13"]{-webkit-box-ordinal-group:-12;-webkit-order:-13;order:-13}[flex-order="-12"]{-webkit-box-ordinal-group:-11;-webkit-order:-12;order:-12}[flex-order="-11"]{-webkit-box-ordinal-group:-10;-webkit-order:-11;order:-11}[flex-order="-10"]{-webkit-box-ordinal-group:-9;-webkit-order:-10;order:-10}[flex-order="-9"]{-webkit-box-ordinal-group:-8;-webkit-order:-9;order:-9}[flex-order="-8"]{-webkit-box-ordinal-group:-7;-webkit-order:-8;order:-8}[flex-order="-7"]{-webkit-box-ordinal-group:-6;-webkit-order:-7;order:-7}[flex-order="-6"]{-webkit-box-ordinal-group:-5;-webkit-order:-6;order:-6}[flex-order="-5"]{-webkit-box-ordinal-group:-4;-webkit-order:-5;order:-5}[flex-order="-4"]{-webkit-box-ordinal-group:-3;-webkit-order:-4;order:-4}[flex-order="-3"]{-webkit-box-ordinal-group:-2;-webkit-order:-3;order:-3}[flex-order="-2"]{-webkit-box-ordinal-group:-1;-webkit-order:-2;order:-2}[flex-order="-1"]{-webkit-box-ordinal-group:0;-webkit-order:-1;order:-1}[flex-order="0"]{-webkit-box-ordinal-group:1;-webkit-order:0;order:0}[flex-order="1"]{-webkit-box-ordinal-group:2;-webkit-order:1;order:1}[flex-order="2"]{-webkit-box-ordinal-group:3;-webkit-order:2;order:2}[flex-order="3"]{-webkit-box-ordinal-group:4;-webkit-order:3;order:3}[flex-order="4"]{-webkit-box-ordinal-group:5;-webkit-order:4;order:4}[flex-order="5"]{-webkit-box-ordinal-group:6;-webkit-order:5;order:5}[flex-order="6"]{-webkit-box-ordinal-group:7;-webkit-order:6;order:6}[flex-order="7"]{-webkit-box-ordinal-group:8;-webkit-order:7;order:7}[flex-order="8"]{-webkit-box-ordinal-group:9;-webkit-order:8;order:8}[flex-order="9"]{-webkit-box-ordinal-group:10;-webkit-order:9;order:9}[flex-order="10"]{-webkit-box-ordinal-group:11;-webkit-order:10;order:10}[flex-order="11"]{-webkit-box-ordinal-group:12;-webkit-order:11;order:11}[flex-order="12"]{-webkit-box-ordinal-group:13;-webkit-order:12;order:12}[flex-order="13"]{-webkit-box-ordinal-group:14;-webkit-order:13;order:13}[flex-order="14"]{-webkit-box-ordinal-group:15;-webkit-order:14;order:14}[flex-order="15"]{-webkit-box-ordinal-group:16;-webkit-order:15;order:15}[flex-order="16"]{-webkit-box-ordinal-group:17;-webkit-order:16;order:16}[flex-order="17"]{-webkit-box-ordinal-group:18;-webkit-order:17;order:17}[flex-order="18"]{-webkit-box-ordinal-group:19;-webkit-order:18;order:18}[flex-order="19"]{-webkit-box-ordinal-group:20;-webkit-order:19;order:19}[flex-order="20"]{-webkit-box-ordinal-group:21;-webkit-order:20;order:20}[flex-offset="0"]{margin-left:0}[flex-offset="5"]{margin-left:5%}[flex-offset="10"]{margin-left:10%}[flex-offset="15"]{margin-left:15%}[flex-offset="20"]{margin-left:20%}[flex-offset="25"]{margin-left:25%}[flex-offset="30"]{margin-left:30%}[flex-offset="35"]{margin-left:35%}[flex-offset="40"]{margin-left:40%}[flex-offset="45"]{margin-left:45%}[flex-offset="50"]{margin-left:50%}[flex-offset="55"]{margin-left:55%}[flex-offset="60"]{margin-left:60%}[flex-offset="65"]{margin-left:65%}[flex-offset="70"]{margin-left:70%}[flex-offset="75"]{margin-left:75%}[flex-offset="80"]{margin-left:80%}[flex-offset="85"]{margin-left:85%}[flex-offset="90"]{margin-left:90%}[flex-offset="95"]{margin-left:95%}[flex-offset="33"]{margin-left:33.3333333333%}[flex-offset="66"]{margin-left:66.6666666667%}[layout-align="start stretch"],[layout-align]{-webkit-box-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start;-webkit-align-content:stretch;align-content:stretch;-webkit-box-align:stretch;-webkit-align-items:stretch;align-items:stretch}[layout-align="start center"],[layout-align="start end"],[layout-align="start start"],[layout-align="start stretch"],[layout-align=start]{-webkit-box-pack:start;-webkit-justify-content:flex-start;justify-content:flex-start}[layout-align="center center"],[layout-align="center end"],[layout-align="center start"],[layout-align="center stretch"],[layout-align=center]{-webkit-box-pack:center;-webkit-justify-content:center;justify-content:center}[layout-align="end center"],[layout-align="end end"],[layout-align="end start"],[layout-align="end stretch"],[layout-align=end]{-webkit-box-pack:end;-webkit-justify-content:flex-end;justify-content:flex-end}[layout-align="spacLine truncated
|
||||
/*# sourceMappingURL=styles.css.map */
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"sources":["css/styles.css"],"names":[],"mappings":"iBAwBwD,UAAW,YAA1C,gBAAZ,WAAb,YAA0C,aACxC,QAAS,eAGX,SACE,QAAS,EAaX,4BACE,cACE,OAAQ,EACR,MAAO,KACP,WAAY,KACZ,OAAQ,MAOZ,aACE,0BAA2B,EAC3B,cAAe,EACP,MAAO,EAGjB,mBACE,0BAA2B,IAC3B,cAAe,IACP,MAAO,IAGjB,mBACE,0BAA2B,IAC3B,cAAe,IACP,MAAO,IAGjB,mBACE,0BAA2B,IAC3B,cAAe,IACP,MAAO,IAGjB,mBACE,0BAA2B,IAC3B,cAAe,IACP,MAAO,IAGjB,mBACE,0BAA2B,IAC3B,cAAe,IACP,MAAO,IAGjB,mBACE,0BAA2B,IAC3B,cAAe,IACP,MAAO,IAGjB,mBACE,0BAA2B,IAC3B,cAAe,IACP,MAAO,IAGjB,mBACE,0BAA2B,IAC3B,cAAe,IACP,MAAO,IAGjB,mBACE,0BAA2B,IAC3B,cAAe,IACP,MAAO,IAGjB,mBACE,0BAA2B,IAC3B,cAAe,IACP,MAAO,IAGjB,mBACE,0BAA2B,GAC3B,cAAe,IACP,MAAO,IAGjB,kBACE,0BAA2B,GAC3B,cAAe,GACP,MAAO,GAGjB,kBACE,0BAA2B,GAC3B,cAAe,GACP,MAAO,GAGjB,kBACE,0BAA2B,GAC3B,cAAe,GACP,MAAO,GAGjB,kBACE,0BAA2B,GAC3B,cAAe,GACP,MAAO,GAGjB,kBACE,0BAA2B,GAC3B,cAAe,GACP,MAAO,GAGjB,kBACE,0BAA2B,GAC3B,cAAe,GACP,MAAO,GAGjB,kBACE,0BAA2B,GAC3B,cAAe,GACP,MAAO,GAGjB,kBACE,0BAA2B,GAC3B,cAAe,GACP,MAAO,GAGjB,kBACE,0BAA2B,EAC3B,cAAe,GACP,MAAO,GAGjB,iBACE,0BAA2B,EAC3B,cAAe,EACP,MAAO,EAGjB,iBACE,0BAA2B,EAC3B,cAAe,EACP,MAAO,EAGjB,iBACE,0BAA2B,EAC3B,cAAe,EACP,MAAO,EAGjB,iBACE,0BAA2B,EAC3B,cAAe,EACP,MAAO,EAGjB,iBACE,0BAA2B,EAC3B,cAAe,EACP,MAAO,EAGjB,iBACE,0BAA2B,EAC3B,cAAe,EACP,MAAO,EAGjB,iBACE,0BAA2B,EAC3B,cAAe,EACP,MAAO,EAGjB,iBACE,0BAA2B,EAC3B,cAAe,EACP,MAAO,EAGjB,iBACE,0BAA2B,EAC3B,cAAe,EACP,MAAO,EAGjB,iBACE,0BAA2B,GAC3B,cAAe,EACP,MAAO,EAGjB,kBACE,0BAA2B,GAC3B,cAAe,GACP,MAAO,GAGjB,kBACE,0BAA2B,GAC3B,cAAe,GACP,MAAO,GAGjB,kBACE,0BAA2B,GAC3B,cAAe,GACP,MAAO,GAGjB,kBACE,0BAA2B,GAC3B,cAAe,GACP,MAAO,GAGjB,kBACE,0BAA2B,GAC3B,cAAe,GACP,MAAO,GAGjB,kBACE,0BAA2B,GAC3B,cAAe,GACP,MAAO,GAGjB,kBACE,0BAA2B,GAC3B,cAAe,GACP,MAAO,GAGjB,kBACE,0BAA2B,GAC3B,cAAe,GACP,MAAO,GAGjB,kBACE,0BAA2B,GAC3B,cAAe,GACP,MAAO,GAGjB,kBACE,0BAA2B,GAC3B,cAAe,GACP,MAAO,GAGjB,kBACE,0BAA2B,GAC3B,cAAe,GACP,MAAO,GAGjB,kBACE,YAAa,EAGf,kBACE,YAAa,GAGf,mBACE,YAAa,IAGf,mBACE,YAAa,IAGf,mBACE,YAAa,IAGf,mBACE,YAAa,IAGf,mBACE,YAAa,IAGf,mBACE,YAAa,IAGf,mBACE,YAAa,IAGf,mBACE,YAAa,IAGf,mBACE,YAAa,IAGf,mBACE,YAAa,IAGf,mBACE,YAAa,IAGf,mBACE,YAAa,IAGf,mBACE,YAAa,IAGf,mBACE,YAAa,IAGf,mBACE,YAAa,IAGf,mBACE,YAAa,IAGf,mBACE,YAAa,IAGf,mBACE,YAAa,IAGf,mBACE,YAAa,eAGf,mBACE,YAAa,eAIf,+BADA,eAEE,iBAAkB,MAClB,wBAAyB,WACjB,gBAAiB,WACzB,sBAAuB,QACf,cAAe,QACvB,kBAAmB,QACnB,oBAAqB,QACb,YAAa,QAKvB,8BACA,2BAFA,6BAGA,+BAJA,qBAKE,iBAAkB,MAClB,wBAAyB,WACjB,gBAAiB,WAK3B,+BACA,4BAFA,8BAGA,gCAJA,sBAKE,iBAAkB,OAClB,wBAAyB,OACjB,gBAAiB,OAI3B,4BAEA,yBADA,2BAEA,6BAJA,mBAKE,iBAAkB,IAClB,wBAAyB,SACjB,gBAAiB,SAI3B,qCAEA,kCADA,oCAEA,sCAJA,4BAKE,wBAAyB,aACjB,gBAAiB,aAI3B,sCAEA,mCADA,qCAEA,uCAJA,6BAKE,iBAAkB,QAClB,wBAAyB,cACjB,gBAAiB,cAI3B,8BACA,2BAEA,oCADA,qCAHA,6BAKE,kBAAmB,MACnB,oBAAqB,WACb,YAAa,WACrB,sBAAuB,WACf,cAAe,WAIzB,+BACA,4BAEA,qCADA,sCAHA,8BAKE,kBAAmB,OACnB,oBAAqB,OACb,YAAa,OACrB,sBAAuB,OACf,cAAe,OACvB,UAAW,KAIb,iCACA,8BAEA,uCADA,wCAHA,gCAKE,UAAW,KACX,mBAAoB,WACZ,WAAY,WAItB,4BACA,yBAEA,kCADA,mCAHA,2BAKE,kBAAmB,IACnB,oBAAqB,SACb,YAAa,SACrB,sBAAuB,SACf,cAAe,SAIzB,gCACA,6BAEA,sCADA,uCAHA,+BAKE,kBAAmB,QACnB,oBAAqB,QACb,YAAa,QACrB,sBAAuB,QACf,cAAe,QAGzB,OACE,iBAAkB,EAClB,aAAc,EACN,KAAM,EACd,mBAAoB,WACZ,WAAY,WAGtB,YACE,iBAAkB,EAClB,aAAc,EAAE,EAAE,KACV,KAAM,EAAE,EAAE,KAClB,mBAAoB,WACZ,WAAY,WAGtB,eACE,iBAAkB,EAClB,aAAc,EAAE,EAAE,KACV,KAAM,EAAE,EAAE,KAClB,mBAAoB,WACZ,WAAY,WAGtB,YACE,iBAAkB,EAClB,aAAc,EAAE,EAAE,KACV,KAAM,EAAE,EAAE,KAClB,mBAAoB,WACZ,WAAY,WAGtB,YACE,iBAAkB,EAClB,aAAc,EAAE,EAAE,KACV,KAAM,EAAE,EAAE,KAClB,mBAAoB,WACZ,WAAY,WAGtB,WACE,iBAAkB,EAClB,aAAc,EAAE,EAAE,GACV,KAAM,EAAE,EAAE,GAClB,UAAW,GACX,WAAY,KACZ,mBAAoB,WACZ,WAAY,WAGtB,wBACE,iBAAkB,EAClB,aAAc,EAAE,EAAE,GACV,KAAM,EAAE,EAAE,GAClB,UAAW,GACX,WAAY,KACZ,mBAAoB,WACZ,WAAY,WAGtB,2BACE,iBAAkB,EAClB,aAAc,EAAE,EAAE,GACV,KAAM,EAAE,EAAE,GAClB,UAAW,KACX,WAAY,GACZ,mBAAoB,WACZ,WAAY,WAGtB,yBACE,iBAAkB,EAClB,aAAc,EAAE,EAAE,OACV,KAAM,EAAE,EAAE,OAClB,UAAW,OACX,WAAY,KACZ,mBAAoB,WACZ,WAAY,WAEtB,yBACE,iBAAkB,EAClB,aAAc,EAAE,EAAE,OACV,KAAM,EAAE,EAAE,OAClB,UAAW,OACX,WAAY,KACZ,mBAAoB,WACZ,WAAY,WAGtB,4BACE,iBAAkB,EAClB,aAAc,EAAE,EAAE,OACV,KAAM,EAAE,EAAE,OAClB,UAAW,KACX,WAAY,OACZ,mBAAoB,WACZ,WAAY,WAEtB,4BACE,iBAAkB,EAClB,aAAc,EAAE,EAAE,OACV,KAAM,EAAE,EAAE,OAClB,UAAW,KACX,WAAY,OACZ,mBAAoB,WACZ,WAAY,WAGtB,wBACE,iBAAkB,EAClB,aAAc,EAAE,EAAE,GACV,KAAM,EAAE,EAAE,GAClB,UAAW,GACX,WAAY,KACZ,mBAAoB,WACZ,WAAY,WAGtB,2BACE,iBAAkB,EAClB,aAAc,EAAE,EAAE,GACV,KAAM,EAAE,EAAE,GAClB,UAAW,KACX,WAAY,GACZ,mBAAoB,WACZ,WAAY,WAGtB,WACE,iBAAkB,EAClB,aAAc,EAAE,EAAE,GACV,KAAM,EAAE,EAAE,GAClB,UAAW,GACX,WAAY,KACZ,mBAAoB,WACZ,WAAY,WAGtB,wBACE,iBAAkB,EAClB,aAAc,EAAE,EAAE,GACV,KAAM,EAAE,EAAE,GAClB,UAAW,GACX,WAAY,KACZ,mBAAoB,WACZ,WAAY,WAGtB,2BACE,iBAAkB,EAClB,aAAc,EAAE,EAAE,GACV,KAAM,EAAE,EAAE,GAClB,UAAW,KACX,WAAY,GACZ,mBAAoB,WACZ,WAAY,WAGtB,yBACE,iBAAkB,EAClB,aAAc,EAAE,EAAE,OACV,KAAM,EAAE,EAAE,OAClB,UAAW,OACX,WAAY,KACZ,mBAAoB,WACZ,WAAY,WAEtB,yBACE,iBAAkB,EAClB,aAAc,EAAE,EAAE,OACV,KAAM,EAAE,EAAE,OAClB,UAAW,OACX,WAAY,KACZ,mBAAoB,WACZ,WAAY,WAGtB,4BACE,iBAAkB,EAClB,aAAc,EAAE,EAAE,OACV,KAAM,EAAE,EAAE,OAClB,UAAW,KACX,WAAY,OACZ,mBAAoBLine truncated
|
||||
{"version":3,"sources":["css/styles.css"],"names":[],"mappings":"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 truncated
|
||||
@@ -1,2 +1,2 @@
|
||||
function l(){var n,e,t=arguments[0],o=t,r=arguments;for(labels[t]?o=labels[t]:clabels[t]&&(o=clabels[t]),n=1,e=0;n<r.length;n++,e++)o=o.replace("%{"+e+"}",r[n]);return o=(n=1)<r.length?o.replace(/%((%)|s|d)/g,function(e){var t=null;return e[2]?t=e[2]:(t=r[n],"%d"===e&&(t=parseFloat(t),isNaN(t))&&(t=0),n++),t}):o}function hexToRgb(e){e=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return e?{r:parseInt(e[1],16),g:parseInt(e[2],16),b:parseInt(e[3],16)}:null}function contrast(e){var t=1,e=hexToRgb(e);if(e){for(var n=[e.r/255,e.g/255,e.b/255],o=0;o<n.length;++o)n[o]=n[o]<=.03928?n[o]/12.92:Math.pow((n[o]+.055)/1.055,2.4);t=.2126*n[0]+.7152*n[1]+.0722*n[2]}return.179<t?"black":"white"}function guid(){function e(){return(65536*(1+Math.random())|0).toString(16).substring(1)}return(e()+e()+"-"+e()+"-4"+e().substr(0,3)+"-"+e()+"-"+e()+e()+e()).toLowerCase()}!function(){"use strict";function e(e,t,n,o,r,i,s){e.classNameFilter(/^(?:(?!ng-animate-disabled).)*$/),r.definePalette("sogo-green",{50:"eaf5e9",100:"cbe5c8",200:"aad6a5",300:"88c781",400:"66b86a",500:"56b04c",600:"4da143",700:"388e3c",800:"367d2e",900:"225e1b",A100:"fafafa",A200:"69f0ae",A400:"00e676",A700:"00c853",contrastDefaultColor:"dark",contrastLightColors:["300","400","500","600","700","800","900"]}),r.definePalette("sogo-blue",{50:"f0faf9",100:"e1f5f3",200:"ceebe8",300:"bfe0dd",400:"b2d6d3",500:"a1ccc8",600:"8ebfbb",700:"7db3b0",800:"639997",900:"4d8080",A100:"d4f7fa",A200:"c3f5fa",A400:"53e3f0",A700:"00b0c0",contrastDefaultColor:"light",contrastDarkColors:["50","100","200"]});e=r.extendPalette("grey",{1e3:"baa870"});r.definePalette("sogo-grey",e),r.theme("default").primaryPalette("sogo-blue",{default:"900","hue-1":"400","hue-2":"800","hue-3":"A700"}).accentPalette("sogo-green",{default:"500","hue-1":"A100","hue-2":"300","hue-3":"A700"}).backgroundPalette("sogo-grey"),r.registerStyles(["md-toolbar.md-hue-1:not(.md-menu-toolbar).md-accent,","md-toolbar.md-hue-1:not(.md-menu-toolbar).md-accent md-input-container[md-no-float] .md-input {"," background-color: '{{accent-hue-1}}';"," color: '{{foreground-1}}';","}","md-toolbar.md-hue-1:not(.md-menu-toolbar).md-accent md-icon {"," color: '{{foreground-1}}';"," fill: '{{foreground-1}}';","}"].join("")),r.registerStyles([".md-autocomplete-suggestions.md-3-line li p {"," color: '{{foreground-2}}';","}"].join("")),r.registerStyles([".sg-time-selection-indicator.sg-time-selected,",".sg-time-selection-indicator:hover.sg-time-selected,",".sg-time-selection-indicator.md-focus.sg-time-selected {"," background: '{{primary-500}}';","}",".sg-timepicker-open .sg-timepicker-icon {"," color: '{{primary-900}}';","}",".sg-timepicker-time,",".sg-timepicker-open .sg-timepicker-input-container {"," background: '{{background-hue-1}}';","}",".sg-timepicker-input-mask-opaque {"," box-shadow: 0 0 0 9999px '{{background-hue-1}}';","}"].join("")),r.registerStyles(["[ui-view=calendars] .hours {"," color: '{{primary-700}}';","}",".attendees .busy {"," background-color: '{{background-1000}}';","}",".attendees .event {"," background-color: '{{primary-300}}';","}"].join("")),r.registerStyles([".sg-message-thread {"," background-color: '{{primary-100}}';","}",".sg-message-thread-first {"," background-color: '{{primary-200}}';","}"].join("")),window.DebugEnabled||(t.debugEnabled(!1),n.debugInfoEnabled(!1),i.disableWarnings(),s.errorOnUnhandledRejections(!1),r.generateThemesOnDemand(!0)),o.interceptors.push("AuthInterceptor"),o.interceptors.push("ErrorInterceptor")}function a(t,e,n,o,r){var i=e.defer(),s=angular.element('<iframe class="ng-hide" src="'+t.UserFolderURL+'recover"></iframe>');return s.on("load",function(){var e=o.get("$state");2<r.config.attempt?(angular.element(t).off("beforeunload"),t.location.href=t.ApplicationBaseURL+e.href(e.current),i.reject()):n(function(){var e=o.get("$http");r.config.attempt?r.config.attempt++:r.config.attempt=1,e(r.config).then(function(e){i.resolve(e)},function(e){i.reject(e)}).finally(function(){n(s.remove,1e3)})},2e3)}),document.body.appendChild(s[0]),i.promise}function t(n,o,r,i){return{response:function(e){var t;return e&&/^application\/json/.test(e.config.headers.Accept)&&/^[\n\r\t ]*<!DOCTYPE html/.test(e.data)?n.usesCASAuthentication||n.usesSAML2Authentication?a(n,o,r,i,e):(t=i.get("$state"),angular.element(n).off("beforeunload"),t.href(t.current)?n.location.href=n.ApplicationBaseURL+t.href(t.current):n.location.href=n.ApplicationBaseURL,o.reject()):e}}}function n(n,o,r,i,s){return{responseError:function(e){var t;if(/^application\/json/.test(e.config.headers.Accept)){if((o.usesCASAuthentication||o.usesSAML2Authentication)&&(-1==e.status||500==e.status&&-1!==document.cookie.indexOf("cas-location")))return a(o,r,i,s,e);o.usesSAML2Authentication&&401==e.status&&!o.recovered?(t=s.get("$state"),angular.element(o).off("beforeunload"),o.recovered=!0,o.location.href=o.ApplicationBaseURL+t.href(t.current)):e.data&&e.data.quiet||n.$broadcast("http:Error",e)}return r.reject(e)}}}angular.Line truncated
|
||||
function l(){var n,e,t=arguments[0],o=t,i=arguments;for(labels[t]?o=labels[t]:clabels[t]&&(o=clabels[t]),n=1,e=0;n<i.length;n++,e++)o=o.replace("%{"+e+"}",i[n]);return o=(n=1)<i.length?o.replace(/%((%)|s|d)/g,function(e){var t=null;return e[2]?t=e[2]:(t=i[n],"%d"===e&&(t=parseFloat(t),isNaN(t))&&(t=0),n++),t}):o}function hexToRgb(e){e=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(e);return e?{r:parseInt(e[1],16),g:parseInt(e[2],16),b:parseInt(e[3],16)}:null}function contrast(e){var t=1,e=hexToRgb(e);if(e){for(var n=[e.r/255,e.g/255,e.b/255],o=0;o<n.length;++o)n[o]=n[o]<=.03928?n[o]/12.92:Math.pow((n[o]+.055)/1.055,2.4);t=.2126*n[0]+.7152*n[1]+.0722*n[2]}return.179<t?"black":"white"}function guid(){function e(){return(65536*(1+Math.random())|0).toString(16).substring(1)}return(e()+e()+"-"+e()+"-4"+e().substr(0,3)+"-"+e()+"-"+e()+e()+e()).toLowerCase()}!function(){"use strict";function e(e,t,n,o,i,r,s){e.classNameFilter(/^(?:(?!ng-animate-disabled).)*$/),i.definePalette("sogo-green",{50:"eaf5e9",100:"cbe5c8",200:"aad6a5",300:"88c781",400:"66b86a",500:"56b04c",600:"4da143",700:"388e3c",800:"367d2e",900:"225e1b",A100:"fafafa",A200:"69f0ae",A400:"00e676",A700:"00c853",contrastDefaultColor:"dark",contrastLightColors:["300","400","500","600","700","800","900"]}),i.definePalette("sogo-blue",{50:"f0faf9",100:"e1f5f3",200:"ceebe8",300:"bfe0dd",400:"b2d6d3",500:"a1ccc8",600:"8ebfbb",700:"7db3b0",800:"639997",900:"4d8080",A100:"d4f7fa",A200:"c3f5fa",A400:"53e3f0",A700:"00b0c0",contrastDefaultColor:"light",contrastDarkColors:["50","100","200"]});e=i.extendPalette("grey",{1e3:"baa870"});i.definePalette("sogo-grey",e),i.theme("default").primaryPalette("sogo-blue",{default:"900","hue-1":"400","hue-2":"800","hue-3":"A700"}).accentPalette("sogo-green",{default:"500","hue-1":"A100","hue-2":"300","hue-3":"A700"}).backgroundPalette("sogo-grey"),i.registerStyles(["md-toolbar.md-hue-1:not(.md-menu-toolbar).md-accent,","md-toolbar.md-hue-1:not(.md-menu-toolbar).md-accent md-input-container[md-no-float] .md-input {"," background-color: '{{accent-hue-1}}';"," color: '{{foreground-1}}';","}","md-toolbar.md-hue-1:not(.md-menu-toolbar).md-accent md-icon {"," color: '{{foreground-1}}';"," fill: '{{foreground-1}}';","}"].join("")),i.registerStyles([".md-autocomplete-suggestions.md-3-line li p {"," color: '{{foreground-2}}';","}"].join("")),i.registerStyles([".sg-time-selection-indicator.sg-time-selected,",".sg-time-selection-indicator:hover.sg-time-selected,",".sg-time-selection-indicator.md-focus.sg-time-selected {"," background: '{{primary-500}}';","}",".sg-timepicker-open .sg-timepicker-icon {"," color: '{{primary-900}}';","}",".sg-timepicker-time,",".sg-timepicker-open .sg-timepicker-input-container {"," background: '{{background-hue-1}}';","}",".sg-timepicker-input-mask-opaque {"," box-shadow: 0 0 0 9999px '{{background-hue-1}}';","}"].join("")),i.registerStyles(["[ui-view=calendars] .hours {"," color: '{{primary-700}}';","}",".attendees .busy {"," background-color: '{{background-1000}}';","}",".attendees .event {"," background-color: '{{primary-300}}';","}"].join("")),i.registerStyles([".sg-message-thread {"," background-color: '{{primary-100}}';","}",".sg-message-thread-first {"," background-color: '{{primary-200}}';","}"].join("")),window.DebugEnabled||(t.debugEnabled(!1),n.debugInfoEnabled(!1),r.disableWarnings(),s.errorOnUnhandledRejections(!1),i.generateThemesOnDemand(!0)),o.interceptors.push("AuthInterceptor"),o.interceptors.push("ErrorInterceptor")}function a(t,e,n,o,i){var r=e.defer(),s=angular.element('<iframe class="ng-hide" src="'+t.UserFolderURL+'recover"></iframe>');return s.on("load",function(){var e=o.get("$state");2<i.config.attempt?(angular.element(t).off("beforeunload"),t.location.href=t.ApplicationBaseURL+e.href(e.current),r.reject()):n(function(){var e=o.get("$http");i.config.attempt?i.config.attempt++:i.config.attempt=1,e(i.config).then(function(e){r.resolve(e)},function(e){r.reject(e)}).finally(function(){n(s.remove,1e3)})},2e3)}),document.body.appendChild(s[0]),r.promise}function t(n,o,i,r){return{response:function(e){var t;return e&&/^application\/json/.test(e.config.headers.Accept)&&/^[\n\r\t ]*<!DOCTYPE html/.test(e.data)?n.usesCASAuthentication||n.usesSAML2Authentication?a(n,o,i,r,e):(t=r.get("$state"),angular.element(n).off("beforeunload"),t.href(t.current)?n.location.href=n.ApplicationBaseURL+t.href(t.current):n.location.href=n.ApplicationBaseURL,o.reject()):e}}}function n(n,o,i,r,s){return{responseError:function(e){var t;if(/^application\/json/.test(e.config.headers.Accept)){if((o.usesCASAuthentication||o.usesSAML2Authentication)&&(-1==e.status||500==e.status&&-1!==document.cookie.indexOf("cas-location")))return a(o,i,r,s,e);o.usesSAML2Authentication&&401==e.status&&!o.recovered?(t=s.get("$state"),angular.element(o).off("beforeunload"),o.recovered=!0,o.location.href=o.ApplicationBaseURL+t.href(t.current)):e.data&&e.data.quiet||n.$broadcast("http:Error",e)}return i.reject(e)}}}angular.Line truncated
|
||||
//# sourceMappingURL=Common.js.map
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"Common.js","sources":["Common/utils.js","Common/Common.app.js","Common/mdColors.app.js","Common/cssescape.filter.js","Common/decodeUri.filter.js","Common/encodeUri.filter.js","Common/ensureTarget.filter.js","Common/loc.filter.js","Common/range.filter.js","Common/txt2html.filter.js","Common/AclController.js","Common/navController.js","Common/Acl.service.js","Common/Authentication.service.js","Common/Dialog.service.js","Common/Gravatar.service.js","Common/Resource.service.js","Common/sgFocus.service.js","Common/sgHotkeys.service.js","Common/sgRippleClick.service.js","Common/User.service.js","Common/sgAutogrow.directive.js","Common/sgAvatarImage.directive.js","Common/sgBlockToggle.directive.js","Common/sgCheckmark.directive.js","Common/sgColorPicker.directive.js","Common/sgCompile.directive.js","Common/sgDatepickerReadonlyInput.directive.js","Common/sgDraggable.directive.js","Common/sgDroppable.directive.js","Common/sgEnter.directive.js","Common/sgEscape.directive.js","Common/sgFocus.directive.js","Common/sgFolderStylesheet.directive.js","Common/sgLabels.directive.js","Common/sgNoDirtyCheck.directive.js","Common/sgPlaceholder.directive.js","Common/sgQrCode.directive.js","Common/sgResetOnDisabled.directive.js","Common/sgRippleClick.directive.js","Common/sgSearch.directive.js","Common/sgSelectOnly.directive.js","Common/sgSubscribe.directive.js","Common/sgTimepicker.directive.js","Common/sgToggleGrid.directive.js"],"names":["l","i","j","key","arguments","value","args","labels","clabels","length","replace","m","val","parseFloat","isNaN","hexToRgb","hex","result","exec","r","parseInt","g","b","contrast","color","c","Math","pow","guid","S4","random","toString","substring","substr","toLowerCase","configure","$animateProvider","$logProvider","$compileProvider","$httpProvider","$mdThemingProvider","$mdAriaProvider","$qProvider","classNameFilter","definePalette","50","100","200","300","400","500","600","700","800","900","A100","A200","A400","A700","contrastDefaultColor","contrastLightColors","contrastDarkColors","greyMap","extendPalette","1000","theme","primaryPalette","default","hue-1","hue-2","hue-3","accentPalette","backgroundPalette","registerStyles","join","window","DebugEnabled","debugEnabled","debugInfoEnabled","disableWarnings","errorOnUnhandledRejections","generateThemesOnDemand","interceptors","push","renewTicket","$window","$q","$timeout","$injector","response","deferred","defer","iframe","angular","element","UserFolderURL","on","$state","get","config","attempt","off","location","href","ApplicationBaseURL","current","reject","$http","then","resolve","finally","remove","document","body","appendChild","promise","AuthInterceptor","test","headers","Accept","data","usesCASAuthentication","usesSAML2Authentication","ErrorInterceptor","$rootScope","responseError","rejection","status","cookie","indexOf","recovered","quiet","$broadcast","module","isPopup","classList","contains","baseURL","resourcesURL","ResourcesURL","activeUser","param","settings","login","UserLogin","identification","UserIdentification","email","UserEmail","language","UserLanguage","folderURL","isSuperUser","IsSuperUser","path","calendar","UserCalendarPath","contacts","UserContactsPath","mail","UserMailPath","preferences","UserPreferencesPath","administration","UserAdministrationPath","help","HelpURL","logoff","UserLogoffPath","minimumSearchLength","isNumber","constant","selection","xs","gt-xs","sm","gt-sm","md","gt-md","lg","gt-lg","xl","print","toastPosition","factory","$inject","_$mdThemingProvider","runBlock","$interpolate","$document","$log","buildCssSelectors","selectors","fgDefault","bgDefault","bdrDefault","fgDefaultHue","bgDefaultHue","fgColor","bgColor","bdrColor","fgHue","bgHue","customSheet","style","head","querySelector","createElement","title","createTextNode","sheet","index","cssRules","deleteRule","addRule","fgInterpolate","bgInterpolate","themeName","paletteName","colorArray","hueName","context","palette","hue","insertRule","addBorderRule","bdrInterpolate","colorValue","forEach","_THEMES","_PALETTES","colors","primary","name","hues","run","cssEscape","CSS","escape","filter","decodeUri","decodeURIComponent","encodeUri","encodeURIComponent","ensureTarget","$sce","tree","find","attr","trustAs","html","n","res","txt2html","linkyFilter","text","String","rel","undefined","AclController","$mdDialog","Dialog","usersWithACL","User","folder","vm","this","selectUser","user","$event","target","parentNode","selectedUid","uid","selectedUser","inactive","$rights","users","userToAdd","searchText","folderClassName","isFunction","getClassName","templateName","getElementById","$isAnonymous","userFilter","$query","$filter","$acl","dry","closeModal","$resetUsersRights","hide","saveModal","$saveUsersRights","alert","confirmChange","confirmation","$confirmRights","showing","message","removeUser","$removeUser","addUser","$addUser","toggleAllRights","$toggleAllRights","hasNoRight","$hasNoRight","showRights","controller","navController",Line truncated
|
||||
{"version":3,"file":"Common.js","sources":["Common/utils.js","Common/Common.app.js","Common/mdColors.app.js","Common/cssescape.filter.js","Common/decodeUri.filter.js","Common/encodeUri.filter.js","Common/ensureTarget.filter.js","Common/loc.filter.js","Common/range.filter.js","Common/txt2html.filter.js","Common/AclController.js","Common/navController.js","Common/Acl.service.js","Common/Authentication.service.js","Common/Dialog.service.js","Common/Gravatar.service.js","Common/Resource.service.js","Common/sgFocus.service.js","Common/sgHotkeys.service.js","Common/sgRippleClick.service.js","Common/User.service.js","Common/sgAutogrow.directive.js","Common/sgAvatarImage.directive.js","Common/sgBlockToggle.directive.js","Common/sgCheckmark.directive.js","Common/sgColorPicker.directive.js","Common/sgCompile.directive.js","Common/sgDatepickerReadonlyInput.directive.js","Common/sgDraggable.directive.js","Common/sgDroppable.directive.js","Common/sgEnter.directive.js","Common/sgEscape.directive.js","Common/sgFocus.directive.js","Common/sgFolderStylesheet.directive.js","Common/sgLabels.directive.js","Common/sgNoDirtyCheck.directive.js","Common/sgPlaceholder.directive.js","Common/sgQrCode.directive.js","Common/sgResetOnDisabled.directive.js","Common/sgRippleClick.directive.js","Common/sgSearch.directive.js","Common/sgSelectOnly.directive.js","Common/sgSubscribe.directive.js","Common/sgTimepicker.directive.js","Common/sgToggleGrid.directive.js"],"names":["l","i","j","key","arguments","value","args","labels","clabels","length","replace","m","val","parseFloat","isNaN","hexToRgb","hex","result","exec","r","parseInt","g","b","contrast","color","c","Math","pow","guid","S4","random","toString","substring","substr","toLowerCase","configure","$animateProvider","$logProvider","$compileProvider","$httpProvider","$mdThemingProvider","$mdAriaProvider","$qProvider","classNameFilter","definePalette","50","100","200","300","400","500","600","700","800","900","A100","A200","A400","A700","contrastDefaultColor","contrastLightColors","contrastDarkColors","greyMap","extendPalette","1000","theme","primaryPalette","default","hue-1","hue-2","hue-3","accentPalette","backgroundPalette","registerStyles","join","window","DebugEnabled","debugEnabled","debugInfoEnabled","disableWarnings","errorOnUnhandledRejections","generateThemesOnDemand","interceptors","push","renewTicket","$window","$q","$timeout","$injector","response","deferred","defer","iframe","angular","element","UserFolderURL","on","$state","get","config","attempt","off","location","href","ApplicationBaseURL","current","reject","$http","then","resolve","finally","remove","document","body","appendChild","promise","AuthInterceptor","test","headers","Accept","data","usesCASAuthentication","usesSAML2Authentication","ErrorInterceptor","$rootScope","responseError","rejection","status","cookie","indexOf","recovered","quiet","$broadcast","module","isPopup","classList","contains","baseURL","resourcesURL","ResourcesURL","activeUser","param","settings","login","UserLogin","identification","UserIdentification","email","UserEmail","language","UserLanguage","folderURL","isSuperUser","IsSuperUser","path","calendar","UserCalendarPath","contacts","UserContactsPath","mail","UserMailPath","preferences","UserPreferencesPath","administration","UserAdministrationPath","help","HelpURL","logoff","UserLogoffPath","minimumSearchLength","isNumber","constant","selection","xs","gt-xs","sm","gt-sm","md","gt-md","lg","gt-lg","xl","print","toastPosition","factory","$inject","_$mdThemingProvider","runBlock","$interpolate","$document","$log","buildCssSelectors","selectors","fgDefault","bgDefault","bdrDefault","fgDefaultHue","bgDefaultHue","fgColor","bgColor","bdrColor","fgHue","bgHue","customSheet","style","head","querySelector","createElement","title","createTextNode","sheet","index","cssRules","deleteRule","addRule","fgInterpolate","bgInterpolate","themeName","paletteName","colorArray","hueName","context","palette","hue","insertRule","addBorderRule","bdrInterpolate","colorValue","forEach","_THEMES","_PALETTES","colors","primary","name","hues","run","cssEscape","CSS","escape","filter","decodeUri","decodeURIComponent","encodeUri","encodeURIComponent","ensureTarget","$sce","tree","find","attr","trustAs","html","n","res","txt2html","linkyFilter","text","String","rel","undefined","AclController","$mdDialog","Dialog","usersWithACL","User","folder","vm","this","selectUser","user","$event","target","parentNode","selectedUid","uid","selectedUser","inactive","$rights","users","userToAdd","searchText","folderClassName","isFunction","getClassName","templateName","getElementById","$isAnonymous","userFilter","$query","$filter","$acl","dry","closeModal","$resetUsersRights","hide","saveModal","$saveUsersRights","alert","confirmChange","confirmation","$confirmRights","showing","message","removeUser","$removeUser","addUser","$addUser","toggleAllRights","$toggleAllRights","hasNoRight","$hasNoRight","showRights","controller","navController",Line truncated
|
||||
@@ -149,12 +149,20 @@
|
||||
perr == passwordPolicyConfig.PolicyChangeAfterReset) {
|
||||
response = {
|
||||
passwordexpired: 1,
|
||||
userPolicies: data.additionalInfos.userPolicies,
|
||||
url: redirectUrl(username, domain)
|
||||
};
|
||||
}
|
||||
else if (perr == passwordPolicyConfig.PolicyChangeAfterReset) {
|
||||
response = {
|
||||
passwordexpired: 1,
|
||||
userPolicies: data.additionalInfos.userPolicies,
|
||||
url: redirectUrl(username, domain)
|
||||
};
|
||||
} else if (perr == passwordPolicyConfig.PolicyInsufficientPasswordQuality) {
|
||||
response = {
|
||||
passwordexpired: 2,
|
||||
userPolicies: data.additionalInfos.userPolicies,
|
||||
url: redirectUrl(username, domain)
|
||||
};
|
||||
}
|
||||
@@ -170,6 +178,45 @@
|
||||
return d.promise;
|
||||
}, // login: function(data) { ...
|
||||
|
||||
loginName: function(data) {
|
||||
var d = $q.defer(),
|
||||
username = data.username,
|
||||
language;
|
||||
|
||||
if (data.language && data.language != 'WONoSelectionString') {
|
||||
language = data.language;
|
||||
}
|
||||
|
||||
$http({
|
||||
method: 'POST',
|
||||
url: '/SOGo/connectName?userName='+username,
|
||||
// data: JSON.stringify({userName: username}),
|
||||
data: {userName: username},
|
||||
// headers: {
|
||||
// //'Content-Type': undefined
|
||||
// //'Content-Type': "application/x-www-form-urlencoded"
|
||||
// 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8'
|
||||
// }
|
||||
}).then(function(response) {
|
||||
var data = response.data;
|
||||
// Make sure browser's cookies are enabled
|
||||
if (navigator && !navigator.cookieEnabled) {
|
||||
d.reject({error: l('cookiesNotEnabled')});
|
||||
}
|
||||
else {
|
||||
if(data.redirect) {
|
||||
//Redirection in case of openID
|
||||
d.resolve({ url: data.redirect });
|
||||
}
|
||||
}
|
||||
}, function(error) {
|
||||
var response, perr, data = error.data;
|
||||
|
||||
d.reject(response);
|
||||
});
|
||||
return d.promise;
|
||||
},
|
||||
|
||||
changePassword: function(userName, domain, newPassword, oldPassword, token) {
|
||||
var d = $q.defer(),
|
||||
xsrfCookie = $cookies.get('XSRF-TOKEN');
|
||||
|
||||
@@ -230,6 +230,7 @@
|
||||
};
|
||||
|
||||
config = vm.config ? angular.merge(sgCkeditorConfig.config, vm.config) : sgCkeditorConfig.config;
|
||||
config.licenseKey = "GPL";
|
||||
|
||||
if (!config.toolbar) {
|
||||
config.toolbar = {
|
||||
@@ -257,6 +258,27 @@
|
||||
}
|
||||
]
|
||||
};
|
||||
config.fontFamily = {
|
||||
supportAllValues: true,
|
||||
style: {
|
||||
element: 'span',
|
||||
attributes: {
|
||||
style: 'font-family'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
config.fontSize = {
|
||||
options: ['10px', '12px', '14px', '16px', '18px', '20px', '24px'],
|
||||
supportAllValues: true,
|
||||
style: {
|
||||
element: 'span',
|
||||
attributes: {
|
||||
style: 'font-size'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
config.htmlEmbed = {
|
||||
showPreviews: true
|
||||
};
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
!function(){"use strict";function c(e){var t=this;"function"!=typeof e.then&&(angular.extend(this,e),_.forEach(this.identities,function(e){var t;e.fullName&&e.email?e.full=e.fullName+" <"+e.email+">":e.email?e.full="<"+e.email+">":e.full="",e.signature&&(t=angular.element("<div>"+e.signature+"</div>"),e.textSignature=_.map(t.contents(),"textContent").join(" ").trim())}),this.$mailboxes)&&c.$Mailbox.$unwrapCollection(this,c.$q.when({mailboxes:this.$mailboxes})).then(function(e){t.$mailboxes=e})}c.$factory=["$q","$timeout","$log","sgSettings","Resource","Preferences","Mailbox","Message",function(e,t,s,n,i,a,o,r){return angular.extend(c,{$q:e,$timeout:t,$log:s,$$resource:new i(n.activeUser("folderURL")+"Mail",n.activeUser()),$Preferences:a,$Mailbox:o,$Message:r}),c}];try{angular.module("SOGo.MailerUI")}catch(e){angular.module("SOGo.MailerUI",["SOGo.Common"])}angular.module("SOGo.MailerUI").factory("Account",c.$factory),c.$findAll=function(e){return e?c.$unwrapCollection(e):c.$accounts?c.$q.when(c.$accounts):c.$$resource.fetch("","mailAccounts").then(function(e){return c.$unwrapCollection(e)})},c.$unwrapCollection=function(e){var s=[];return angular.forEach(e,function(e,t){e.id=t,s[t]=new c(e)}),c.$accounts=s},c.refreshUnseenCount=function(e){var t,s=1===c.$Preferences.defaults.SOGoMailFetchAllUnseenCountFolders,n=c.$Preferences.defaults.SOGoRefreshViewCheck;if(s)t=[];else{if(!e)throw Error("SOGoMailFetchAllUnseenCountFolders is disabled and no folders list provided");t=e}_.forEach(c.$accounts,function(e){s?_.forEach(e.$$flattenMailboxes,function(e){t.push(e.id)}):(_.includes(t,e.id+"/folderINBOX")||t.push(e.id+"/folderINBOX"),_.forEach(e.$$flattenMailboxes,function(e){angular.isDefined(e.unseenCount)&&!_.includes(t,e.id)&&t.push(e.id)}))}),c.$$resource.post("","unseenCount",{mailboxes:t}).then(function(t){_.forEach(c.$accounts,function(e){_.forEach(e.$$flattenMailboxes,function(e){angular.isDefined(t[e.id])&&(e.unseenCount=t[e.id])})})}),n&&"manually"!=n&&(c.$refreshUnseenCount&&c.$timeout.cancel(c.$refreshUnseenCount),c.$refreshUnseenCount=c.$timeout(angular.bind(this,c.refreshUnseenCount,e),1e3*n.timeInterval()))},c.prototype.getLength=function(){return this.$expanded?this.$flattenMailboxes().length:0},c.prototype.getItemAtIndex=function(e){var t=this.$flattenMailboxes();return 0<=e&&e<t.length?t[e]:null},c.prototype.$getMailboxes=function(e){var a=this,t=e&&e.reload;return this.$mailboxes&&!t?c.$q.when(this.$mailboxes):(!t&&this.$futureMailboxesData||(this.$futureMailboxesData=c.$Mailbox.$find(this,e).then(function(e){function s(e){_.forEach(e,function(e){var t=_.find(n,["id",e.id]);t&&(e.unseenCount=t.unseenCount),e.children&&0<e.children.length&&s(e.children)})}var n=a.$flattenMailboxes({all:!0});a.$mailboxes=e,a.$expanded=!1;s(a.$mailboxes);function t(e){_.forEach(e,function(e){e.$expanded=0<=i.indexOf("/"+e.id),e.children&&0<e.children.length&&t(e.children)})}var i;if(c.$Preferences.settings.Mail.ExpandedFolders){if(angular.isString(c.$Preferences.settings.Mail.ExpandedFolders))try{i=angular.fromJson(c.$Preferences.settings.Mail.ExpandedFolders)}catch(e){c.$log.warn("Can't parse list of expanded folders. String was: "+c.$Preferences.settings.Mail.ExpandedFolders),i=[]}else i=c.$Preferences.settings.Mail.ExpandedFolders;a.$expanded=0<=i.indexOf("/"+a.id),0<i.length&&t(a.$mailboxes)}return c.$accounts&&(a.$expanded|=1==c.$accounts.length),a.$flattenMailboxes({reload:!0}),a.$mailboxes})),this.$futureMailboxesData)},c.prototype.$flattenMailboxes=function(t){function s(e){_.forEach(e,function(e){n.push(e),(t&&t.all||e.$expanded)&&e.children&&0<e.children.length&&s(e.children)})}var n=[],i=[];return!this.$$flattenMailboxes||t&&(t.reload||t.all)?(s(this.$mailboxes),t&&t.all||(this.$$flattenMailboxes=n,t&&t.saveState&&(_.forEach(c.$accounts,function(e){e.$expanded&&i.push("/"+e.id),_.reduce(e.$$flattenMailboxes,function(e,t){return t.$expanded&&e.push("/"+t.id),e},i)}),c.$$resource.post(null,"saveFoldersState",i)))):n=this.$$flattenMailboxes,n},c.prototype.$getMailboxByType=function(s){function n(e){var t=_.find(e,function(e){return e.type==s});return t||angular.forEach(e,function(e){!t&&e.children&&0<e.children.length&&(t=n(e.children))}),t}return n(this.$mailboxes)},c.prototype.$getMailboxByPath=function(s){function n(e){var t=_.find(e,function(e){return e.path==s});return t||angular.forEach(e,function(e){!t&&e.children&&0<e.children.length&&(t=n(e.children))}),t}var e;if(null==(e=n(this.$mailboxes)))throw Error("No mailbox found matching path "+s);return e},c.prototype.$newMailbox=function(e,t){var s=this;return c.$$resource.post(e.toString(),"createFolder",{name:t}).then(function(){s.$getMailboxes({reload:!0})})},c.prototype.getTextSignature=function(e){var t;return e.signature?(t=angular.element("<div>"+e.signature+"</div>"),e.textSignature=_.map(t.contents(),"textContent").join(" ").trim()):e.textSignature="",e.textSignature},c.prototype.$hasCertificate=function(){return this.security&&this.security.hasCLine truncated
|
||||
!function(){"use strict";function c(e){var t=this;"function"!=typeof e.then&&(angular.extend(this,e),_.forEach(this.identities,function(e){var t;e.fullName&&e.email?e.full=e.fullName+" <"+e.email+">":e.email?e.full="<"+e.email+">":e.full="",e.signature&&(t=angular.element("<div>"+e.signature+"</div>"),e.textSignature=_.map(t.contents(),"textContent").join(" ").trim())}),this.$mailboxes)&&c.$Mailbox.$unwrapCollection(this,c.$q.when({mailboxes:this.$mailboxes})).then(function(e){t.$mailboxes=e})}c.$factory=["$q","$timeout","$log","sgSettings","Resource","Preferences","Mailbox","Message",function(e,t,s,n,i,a,o,r){return angular.extend(c,{$q:e,$timeout:t,$log:s,$$resource:new i(n.activeUser("folderURL")+"Mail",n.activeUser()),$Preferences:a,$Mailbox:o,$Message:r}),c}];try{angular.module("SOGo.MailerUI")}catch(e){angular.module("SOGo.MailerUI",["SOGo.Common"])}angular.module("SOGo.MailerUI").factory("Account",c.$factory),c.$findAll=function(e){return e?c.$unwrapCollection(e):c.$accounts?c.$q.when(c.$accounts):c.$$resource.fetch("","mailAccounts").then(function(e){return c.$unwrapCollection(e)})},c.$unwrapCollection=function(e){var s=[];return angular.forEach(e,function(e,t){e.id=t,s[t]=new c(e)}),c.$accounts=s},c.refreshUnseenCount=function(e){var t,s=1===c.$Preferences.defaults.SOGoMailFetchAllUnseenCountFolders,n=c.$Preferences.defaults.SOGoRefreshViewCheck;if(s)t=[];else{if(!e)throw Error("SOGoMailFetchAllUnseenCountFolders is disabled and no folders list provided");t=e}_.forEach(c.$accounts,function(e){s?_.forEach(e.$$flattenMailboxes,function(e){t.push(e.id)}):(_.includes(t,e.id+"/folderINBOX")||t.push(e.id+"/folderINBOX"),_.forEach(e.$$flattenMailboxes,function(e){angular.isDefined(e.unseenCount)&&!_.includes(t,e.id)&&t.push(e.id)}))}),c.$$resource.post("","unseenCount",{mailboxes:t}).then(function(t){_.forEach(c.$accounts,function(e){_.forEach(e.$$flattenMailboxes,function(e){angular.isDefined(t[e.id])&&(e.unseenCount=t[e.id])})})}),n&&"manually"!=n&&(c.$refreshUnseenCount&&c.$timeout.cancel(c.$refreshUnseenCount),c.$refreshUnseenCount=c.$timeout(angular.bind(this,c.refreshUnseenCount,e),1e3*n.timeInterval()))},c.prototype.getLength=function(){return this.$expanded?this.$flattenMailboxes().length:0},c.prototype.getItemAtIndex=function(e){var t=this.$flattenMailboxes();return 0<=e&&e<t.length?t[e]:null},c.prototype.$getMailboxes=function(e){var a=this,t=e&&e.reload;return this.$mailboxes&&!t?c.$q.when(this.$mailboxes):(!t&&this.$futureMailboxesData||(this.$futureMailboxesData=c.$Mailbox.$find(this,e).then(function(e){function s(e){_.forEach(e,function(e){var t=_.find(n,["id",e.id]);t&&(e.unseenCount=t.unseenCount),e.children&&0<e.children.length&&s(e.children)})}var n=a.$flattenMailboxes({all:!0});a.$mailboxes=e,a.$expanded=!1;s(a.$mailboxes);function t(e){_.forEach(e,function(e){e.$expanded=0<=i.indexOf("/"+e.id),e.children&&0<e.children.length&&t(e.children)})}var i;if(c.$Preferences.settings.Mail.ExpandedFolders){if(angular.isString(c.$Preferences.settings.Mail.ExpandedFolders))try{i=angular.fromJson(c.$Preferences.settings.Mail.ExpandedFolders)}catch(e){c.$log.warn("Can't parse list of expanded folders. String was: "+c.$Preferences.settings.Mail.ExpandedFolders),i=[]}else i=c.$Preferences.settings.Mail.ExpandedFolders;a.$expanded=0<=i.indexOf("/"+a.id),0<i.length&&t(a.$mailboxes)}return c.$accounts&&(a.$expanded|=1==c.$accounts.length),a.$flattenMailboxes({reload:!0}),a.$mailboxes})),this.$futureMailboxesData)},c.prototype.$flattenMailboxes=function(t){function s(e){_.forEach(e,function(e){n.push(e),(t&&t.all||e.$expanded)&&e.children&&0<e.children.length&&s(e.children)})}var n=[],i=[];return!this.$$flattenMailboxes||t&&(t.reload||t.all)?(s(this.$mailboxes),t&&t.all||(this.$$flattenMailboxes=n,t&&t.saveState&&(_.forEach(c.$accounts,function(e){e.$expanded&&i.push("/"+e.id),_.reduce(e.$$flattenMailboxes,function(e,t){return t.$expanded&&e.push("/"+t.id),e},i)}),c.$$resource.post(null,"saveFoldersState",i)))):n=this.$$flattenMailboxes,n},c.prototype.$getMailboxByType=function(s){function n(e){var t=_.find(e,function(e){return e.type==s});return t||angular.forEach(e,function(e){!t&&e.children&&0<e.children.length&&(t=n(e.children))}),t}return n(this.$mailboxes)},c.prototype.$getMailboxByPath=function(s){function n(e){var t=_.find(e,function(e){return e.path==s});return t||angular.forEach(e,function(e){!t&&e.children&&0<e.children.length&&(t=n(e.children))}),t}var e;if(null==(e=n(this.$mailboxes)))throw Error("No mailbox found matching path "+s);return e},c.prototype.$newMailbox=function(e,t){var s=this;return c.$$resource.post(e.toString(),"createFolder",{name:t}).then(function(){s.$getMailboxes({reload:!0})})},c.prototype.getTextSignature=function(e){var t;return e.signature?(t=angular.element("<div>"+e.signature+"</div>"),e.textSignature=_.map(t.contents(),"textContent").join(" ").trim()):e.textSignature="",e.textSignature},c.prototype.$hasCertificate=function(){return this.security&&this.security.hasCLine truncated
|
||||
//# sourceMappingURL=Mailer.services.js.map
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"Mailer.services.js","sources":["Mailer/Account.service.js","Mailer/Mailbox.service.js","Mailer/Message.service.js","Mailer/sgImageGallery.service.js","Mailer/VirtualMailbox.service.js","Mailer/MailboxController.js","Mailer/MailboxesController.js","Mailer/MessageController.js","Mailer/MessageEditorController.js","Mailer/sgAccountSection.directive.js","Mailer/sgIMIP.directive.js","Mailer/sgMailboxListItem.directive.js","Mailer/sgMessageListItem.directive.js","Mailer/sgMessageListItemMain.directive.js","Mailer/sgZoomableImage.directive.js"],"names":["Account","futureAccountData","_this","this","then","angular","extend","_","forEach","identities","identity","element","fullName","email","full","signature","textSignature","map","contents","join","trim","$mailboxes","$Mailbox","$unwrapCollection","$q","when","mailboxes","collection","$factory","$timeout","$log","Settings","Resource","Preferences","Mailbox","Message","$$resource","activeUser","$Preferences","$Message","module","e","factory","$findAll","data","$accounts","fetch","o","i","id","refreshUnseenCount","folders","unseenCountFolders","fetchAllUnseenCountFolders","defaults","SOGoMailFetchAllUnseenCountFolders","refreshViewCheck","SOGoRefreshViewCheck","Error","account","$$flattenMailboxes","mailbox","push","includes","isDefined","unseenCount","post","$refreshUnseenCount","cancel","bind","timeInterval","prototype","getLength","$expanded","$flattenMailboxes","length","getItemAtIndex","index","expandedMailboxes","$getMailboxes","options","reload","$futureMailboxesData","$find","_visitForUnseencount","previousMailbox","find","previousMailboxes","children","all","_visitForExpanded","expandedFolders","indexOf","settings","Mail","ExpandedFolders","isString","fromJson","warn","_visit","allMailboxes","saveState","reduce","$getMailboxByType","type","_find","$getMailboxByPath","path","$newMailbox","name","toString","getTextSignature","$hasCertificate","security","hasCertificate","$certificate","$$certificate","reject","$removeCertificate","updateQuota","percent","description","maxQuota","Math","round","usedSpace","l","formatted","maxMessages","messagesCount","$quota","$newMessage","debug","JSON","stringify","undefined","accountId","mailboxPath","message","$absolutePath","asDraft","accountDefaults","AuxiliaryMailAccounts","alwaysSign","sign","alwaysEncrypt","encrypt","editable","isNew","mailto","isObject","$parseMailto","$addDelegate","user","deferred","defer","param","uid","delegates","resolve","users","status","promise","$removeDelegate","splice","$omit","deep","defaultIdentity","value","key","copy","isReadOnly","pick","isDefault","forceDefaultIdentity","futureMailboxData","$account","init","newMailboxData","create","$unwrap","$rootScope","Acl","PRELOAD","BATCH_DELETE_LIMIT","$$Acl","$query","sort","asc","selectedFolder","$refreshTimeout","$virtualMode","$virtualPath","$searchMode","SortingState","parseInt","constant","LOOKAHEAD","SIZE","createMailboxes","level","isSentFolder","isDraftsFolder","quotas","split","component","asCSSIdentifier","isUndefined","uidsMap","headers","$isLoading","$messages","$visibleMessages","$selectedMessages","$highlightWords","$id","$acl","threaded","$collapsedThreads","threadsCollapsed","$displayName","$isEditable","isEditable","$isSpecial","$icon","$isNoInferiors","isNoInferiors","$shadowData","selectFolder","setSearchMode","searchMode","$lastVisibleIndex","max","$loadMessage","selectedMessages","updateCache","filter","selected","selectedCount","$unselectMessages","isSelectedMessage","messageId","$selectedMessage","selectedMessage","$selectedMessageIndex","hasSelectedMessage","$filter","sortingAttributes","filters","action","labels","searchBy","searchInput","word","cleanedWord","toLowerCase","match","secondFilter","$flaggedOnly","$unseenOnly","$syncToken","syncToken","unseenOnly","flaggedOnly","keys","$filteredLabels","k","f","endIndex","uids","startIndex","loaded","subject","min","loading","futureHeadersData","$unwrapHeaders","flags","isNoSelect","isWritable","getClassName","base","$rename","findParent","parent","parentMailbox","$save","_updateChildren","child","replace","pathRE","oldPath","sibling","localeCompare","RegExp","$compact","$canFolderAs","$setFolderAs","$empty","capitalize","substring","$markAsRead","isread","getLabels","$labels","filteredByLabel","$flagMessages","messages","operation","msgUIDs","forwardMessages","saveSelectedMessages","selectedUIDs","download","exportFolder","filename","open","$delete","$_deleteMessages","firstIndex","forEachRight","selectedIndex","findIndex","updateVisibleMessages","$deleteMessages","batchSize","_deleteMessages","start","end","currentUids","slice","$markOrUnMarkMessagesAsJunk","method","$copyMessages","folder","$moveMessages","$move","parentPath","finally","save","response","error","$reset","Object","assign","childrenArray","collapsedThread","msg","first","collapsed","$broadcast","$futureMailboxData","headersFields","msgObject","hasNewMessages","$topIndex","deleted","jLine truncated
|
||||
{"version":3,"file":"Mailer.services.js","sources":["Mailer/Account.service.js","Mailer/Mailbox.service.js","Mailer/Message.service.js","Mailer/sgImageGallery.service.js","Mailer/VirtualMailbox.service.js","Mailer/MailboxController.js","Mailer/MailboxesController.js","Mailer/MessageController.js","Mailer/MessageEditorController.js","Mailer/sgAccountSection.directive.js","Mailer/sgIMIP.directive.js","Mailer/sgMailboxListItem.directive.js","Mailer/sgMessageListItem.directive.js","Mailer/sgMessageListItemMain.directive.js","Mailer/sgZoomableImage.directive.js"],"names":["Account","futureAccountData","_this","this","then","angular","extend","_","forEach","identities","identity","element","fullName","email","full","signature","textSignature","map","contents","join","trim","$mailboxes","$Mailbox","$unwrapCollection","$q","when","mailboxes","collection","$factory","$timeout","$log","Settings","Resource","Preferences","Mailbox","Message","$$resource","activeUser","$Preferences","$Message","module","e","factory","$findAll","data","$accounts","fetch","o","i","id","refreshUnseenCount","folders","unseenCountFolders","fetchAllUnseenCountFolders","defaults","SOGoMailFetchAllUnseenCountFolders","refreshViewCheck","SOGoRefreshViewCheck","Error","account","$$flattenMailboxes","mailbox","push","includes","isDefined","unseenCount","post","$refreshUnseenCount","cancel","bind","timeInterval","prototype","getLength","$expanded","$flattenMailboxes","length","getItemAtIndex","index","expandedMailboxes","$getMailboxes","options","reload","$futureMailboxesData","$find","_visitForUnseencount","previousMailbox","find","previousMailboxes","children","all","_visitForExpanded","expandedFolders","indexOf","settings","Mail","ExpandedFolders","isString","fromJson","warn","_visit","allMailboxes","saveState","reduce","$getMailboxByType","type","_find","$getMailboxByPath","path","$newMailbox","name","toString","getTextSignature","$hasCertificate","security","hasCertificate","$certificate","$$certificate","reject","$removeCertificate","updateQuota","percent","description","maxQuota","Math","round","usedSpace","l","formatted","maxMessages","messagesCount","$quota","$newMessage","debug","JSON","stringify","undefined","accountId","mailboxPath","message","$absolutePath","asDraft","accountDefaults","AuxiliaryMailAccounts","alwaysSign","sign","alwaysEncrypt","encrypt","editable","isNew","mailto","isObject","$parseMailto","$addDelegate","user","deferred","defer","param","uid","delegates","resolve","users","status","promise","$removeDelegate","splice","$omit","deep","defaultIdentity","value","key","copy","isReadOnly","pick","isDefault","forceDefaultIdentity","futureMailboxData","$account","init","newMailboxData","create","$unwrap","$rootScope","Acl","PRELOAD","BATCH_DELETE_LIMIT","$$Acl","$query","sort","asc","selectedFolder","$refreshTimeout","$virtualMode","$virtualPath","$searchMode","SortingState","parseInt","constant","LOOKAHEAD","SIZE","createMailboxes","level","isSentFolder","isDraftsFolder","quotas","split","component","asCSSIdentifier","isUndefined","uidsMap","headers","$isLoading","$messages","$visibleMessages","$selectedMessages","$highlightWords","$id","$acl","threaded","$collapsedThreads","threadsCollapsed","$displayName","$isEditable","isEditable","$isSpecial","$icon","$isNoInferiors","isNoInferiors","$shadowData","selectFolder","setSearchMode","searchMode","$lastVisibleIndex","max","$loadMessage","selectedMessages","updateCache","filter","selected","selectedCount","$unselectMessages","isSelectedMessage","messageId","$selectedMessage","selectedMessage","$selectedMessageIndex","hasSelectedMessage","$filter","sortingAttributes","filters","action","labels","searchBy","searchInput","word","cleanedWord","toLowerCase","match","secondFilter","$flaggedOnly","$unseenOnly","$syncToken","syncToken","unseenOnly","flaggedOnly","keys","$filteredLabels","k","f","endIndex","uids","startIndex","loaded","subject","min","loading","futureHeadersData","$unwrapHeaders","flags","isNoSelect","isWritable","getClassName","base","$rename","findParent","parent","parentMailbox","$save","_updateChildren","child","replace","pathRE","oldPath","sibling","localeCompare","RegExp","$compact","$canFolderAs","$setFolderAs","$empty","capitalize","substring","$markAsRead","isread","getLabels","$labels","filteredByLabel","$flagMessages","messages","operation","msgUIDs","forwardMessages","saveSelectedMessages","selectedUIDs","download","exportFolder","filename","open","$delete","$_deleteMessages","firstIndex","forEachRight","selectedIndex","findIndex","updateVisibleMessages","$deleteMessages","batchSize","_deleteMessages","start","end","currentUids","slice","$markOrUnMarkMessagesAsJunk","method","$copyMessages","folder","$moveMessages","$move","parentPath","finally","save","response","error","$reset","Object","assign","childrenArray","collapsedThread","msg","first","collapsed","$broadcast","$futureMailboxData","headersFields","msgObject","hasNewMessages","$topIndex","deleted","jLine truncated
|
||||
@@ -405,6 +405,7 @@
|
||||
this.$highlightWords = [];
|
||||
filters.forEach(filter => {
|
||||
if ("subject_or_from" == filter.searchBy
|
||||
|| "subject_or_to" == filter.searchBy
|
||||
|| "contains" == filter.searchBy
|
||||
|| "body" == filter.searchBy
|
||||
|| "from" == filter.searchBy
|
||||
@@ -1043,6 +1044,7 @@
|
||||
this.account = account;
|
||||
if (options && options.unseenCount) {
|
||||
this.unseenCount = options.unseenCount;
|
||||
delete options["unseenCount"];
|
||||
}
|
||||
if (options && options.filter) {
|
||||
this.$messages = [];
|
||||
@@ -1312,4 +1314,21 @@
|
||||
Mailbox.prototype.getHighlightWords = function () {
|
||||
return this.$highlightWords;
|
||||
};
|
||||
|
||||
/**
|
||||
* @function cleanMailbox
|
||||
* @memberof Mailbox.prototype
|
||||
* @desc Cleans up the mailbox by applying the specified parameters.
|
||||
* This can include operations such as moving emails to trash,
|
||||
* filtering emails based on a duration, and applying actions to subfolders.
|
||||
* @param {Object} parameters - An object containing the parameters for cleaning the mailbox.
|
||||
* @param {boolean} [parameters.applyToSubfolders=false] - Whether to apply the cleaning operation to subfolders.
|
||||
* @param {boolean} [parameters.moveToTrash=false] - Whether to move the emails to the trash folder.
|
||||
* @param {string|null} [parameters.filterDuration=null] - A duration filter (e.g., "3m", "6m") to select emails older than the specified duration.
|
||||
* @returns {Promise} A promise that resolves when the cleaning operation is completed, or rejects with an error if the operation fails.
|
||||
*/
|
||||
Mailbox.prototype.cleanMailbox = function (parameters) {
|
||||
return parameters.folders.length > 0 ? Mailbox.$$resource.post(this.id.split("/")[0], 'cleanMailbox', parameters) : Mailbox.$$resource.post(this.id, 'cleanMailbox', parameters);
|
||||
};
|
||||
|
||||
})();
|
||||
@@ -210,9 +210,7 @@
|
||||
}
|
||||
url = url.join('/');
|
||||
popupWindow = $window.open(url, wId,
|
||||
["width=680",
|
||||
"height=520",
|
||||
"resizable=1",
|
||||
["resizable=1",
|
||||
"scrollbars=1",
|
||||
"toolbar=0",
|
||||
"location=0",
|
||||
@@ -589,9 +587,12 @@
|
||||
});
|
||||
};
|
||||
|
||||
this.moveSelectedMessages = function(dstFolder) {
|
||||
this.moveSelectedMessages = function(dstFolder, message) {
|
||||
var moveSelectedMessage = vm.selectedFolder.hasSelectedMessage();
|
||||
var selectedMessages = vm.selectedFolder.selectedMessages();
|
||||
if (message) {
|
||||
selectedMessages.push(message);
|
||||
}
|
||||
var count = vm.selectedFolder.selectedCount();
|
||||
if (_.size(selectedMessages) > 0)
|
||||
vm.selectedFolder.$moveMessages(selectedMessages, '/' + dstFolder).then(function(index) {
|
||||
|
||||
@@ -53,6 +53,10 @@
|
||||
$rootScope.$on('resetMailAdvancedSearchPanel', function () {
|
||||
vm.reset();
|
||||
});
|
||||
|
||||
$rootScope.$on('showCleanMailboxPanel', function (e, d) {
|
||||
vm.showCleanMailboxPanel(d.folder, d.account);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -447,6 +451,128 @@
|
||||
});
|
||||
};
|
||||
|
||||
this.showCleanMailboxPanel = function (folder, account) {
|
||||
// Close sidenav on small devices
|
||||
if (!$mdMedia(sgConstant['gt-md']))
|
||||
$mdSidenav('left').close();
|
||||
|
||||
$mdDialog.show({
|
||||
template: document.getElementById('cleanMailbox').innerHTML,
|
||||
parent: angular.element(document.body),
|
||||
controller: function () {
|
||||
var dialogCtrl = this;
|
||||
|
||||
this.$onInit = function () {
|
||||
this.mainController = vm;
|
||||
this.folder = folder;
|
||||
this.isMailbox = (folder ? false : true);
|
||||
this.name = folder ? folder.$displayName : account.name;
|
||||
this.loading = false;
|
||||
this.date = null;
|
||||
this.form = {
|
||||
filterDuration: "3m",
|
||||
permanentlyDelete: false,
|
||||
confirmDelete: false,
|
||||
filterDurationDate: null
|
||||
};
|
||||
|
||||
var today = new Date();
|
||||
var maxDate = new Date(today);
|
||||
maxDate.setMonth(today.getMonth() - 3);
|
||||
this.maxDate = maxDate;
|
||||
};
|
||||
|
||||
dialogCtrl.closeDialog = function () {
|
||||
$mdDialog.hide();
|
||||
};
|
||||
|
||||
dialogCtrl.isLoading = function () {
|
||||
return this.loading;
|
||||
}
|
||||
|
||||
dialogCtrl.isWarningDisplayed = function () {
|
||||
return (this.form && this.form.permanentlyDelete);
|
||||
}
|
||||
|
||||
dialogCtrl.isApplyDisabled = function () {
|
||||
return !(!this.loading
|
||||
&& (!this.form.permanentlyDelete || (this.form.permanentlyDelete && this.form.confirmDelete))
|
||||
&& (this.form.filterDuration != 'custom' || (this.form.filterDuration == 'custom' && this.form.filterDurationDate))
|
||||
);
|
||||
}
|
||||
|
||||
dialogCtrl.apply = function () {
|
||||
var folders = [];
|
||||
var i;
|
||||
if (account) {
|
||||
for (i = 0; i < account.$mailboxes.length ; i++) {
|
||||
folders.push(account.$mailboxes[i].id);
|
||||
}
|
||||
this.folder = account.$mailboxes[0];
|
||||
}
|
||||
var date = '';
|
||||
var durationMonth = 12;
|
||||
var date = new Date();
|
||||
switch (this.form.filterDuration) {
|
||||
case '3m':
|
||||
durationMonth = 3;
|
||||
date.setMonth(date.getMonth() - durationMonth);
|
||||
break;
|
||||
case '6m':
|
||||
durationMonth = 6;
|
||||
date.setMonth(date.getMonth() - durationMonth);
|
||||
break;
|
||||
case '9m':
|
||||
durationMonth = 9;
|
||||
date.setMonth(date.getMonth() - durationMonth);
|
||||
break;
|
||||
case '1y':
|
||||
durationMonth = 12;
|
||||
date.setMonth(date.getMonth() - durationMonth);
|
||||
break;
|
||||
case 'custom':
|
||||
date = this.form.filterDurationDate;
|
||||
break;
|
||||
}
|
||||
var year = date.getFullYear();
|
||||
var month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
var day = String(date.getDate()).padStart(2, '0');
|
||||
this.date = `${year}-${month}-${day}`;
|
||||
this.folder.cleanMailbox({
|
||||
'applyToSubfolders': (this.form && this.form.applyToSubfolders) ? this.form.applyToSubfolders : false,
|
||||
'permanentlyDelete': (this.form && this.form.permanentlyDelete) ? this.form.permanentlyDelete : false,
|
||||
'date': this.date,
|
||||
'folders': folders
|
||||
}).then(function (data) {
|
||||
dialogCtrl.loading = true;
|
||||
Mailbox.selectedFolder.$filter({
|
||||
"sort": "date",
|
||||
"asc": false,
|
||||
"match": "OR"
|
||||
}).then(function () {
|
||||
$state.go('mail.account.mailbox', { accountId: vm.accounts[0].id, mailboxId: encodeUriFilter(Mailbox.selectedFolder.path) });
|
||||
dialogCtrl.loading = false;
|
||||
$mdDialog.hide();
|
||||
|
||||
$mdToast.show(
|
||||
$mdToast.simple()
|
||||
.textContent(l('%{0} message(s) deleted', data.nbMessageDeleted))
|
||||
.position(sgConstant.toastPosition)
|
||||
.hideDelay(2000));
|
||||
});
|
||||
}).catch(function () {
|
||||
dialogCtrl.loading = false;
|
||||
$mdDialog.hide();
|
||||
});
|
||||
};
|
||||
},
|
||||
controllerAs: 'dialogCtrl',
|
||||
clickOutsideToClose: false,
|
||||
escapeToClose: false,
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
this.delegate = function(account) {
|
||||
$mdDialog.show({
|
||||
templateUrl: account.id + '/delegation', // UI/Templates/MailerUI/UIxMailUserDelegation.wox
|
||||
|
||||
@@ -271,6 +271,14 @@
|
||||
});
|
||||
};
|
||||
|
||||
this.cleanMailbox = function () {
|
||||
// Close sidenav on small devices
|
||||
if (!$mdMedia(sgConstant['gt-md']))
|
||||
$mdSidenav('left').close();
|
||||
|
||||
$rootScope.$broadcast('showCleanMailboxPanel', {folder: this.folder, account: null}); // Show remove old emails panel (broadcast event to MailboxesController)
|
||||
};
|
||||
|
||||
this.emptyJunkFolder = function() {
|
||||
return this.emptyFolder(l('Junk folder emptied'));
|
||||
};
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
!function(){"use strict";angular.module("SOGo.MainUI",["SOGo.Common","SOGo.Authentication"]);function e(e,o,s,r,a,n,i,t){var d=this;this.$onInit=function(){this.creds={username:o.cookieUsername,password:null,domain:null,rememberLogin:angular.isDefined(o.cookieUsername)&&0<o.cookieUsername.length},/\blanguage=/.test(o.location.search)&&(this.creds.language=o.language),this.loginState=!1,this.verificationCodePattern="\\d{6}",this.passwords={newPassword:null,newPasswordConfirmation:null,oldPassword:null,visible:!1},this.passwordRecovery={passwordRecoveryEnabled:!1,passwordRecoveryQuestionKey:null,passwordRecoveryQuestion:null,passwordRecoveryMode:null,passwordRecoveryQuestionAnswer:null,passwordRecoveryToken:null,passwordRecoveryLinkTimer:null,passwordRecoverySecondaryEmailText:null,passwordRecoveryMailDomain:null,showLoader:!1},this.showLogin=!1,s(function(){d.showLogin=!0;var e=window.location.search;let o=new URLSearchParams(e).get("token");0<window.location.pathname.indexOf("passwordRecoveryEmail")&&o?3===(e=(o=o.replace(/\//g,"")).split(".")).length&&(d.passwordRecovery.passwordRecoveryToken=o,e=JSON.parse(atob(e[1])),d.creds.username=e.username,d.creds.domain=e.domain,d.passwordRecovery.passwordRecoveryToken=o,d.passwordRecovery.passwordRecoveryMode="SecondaryEmail",d.passwordRecovery.passwordRecoveryEnabled=!0,d.loginState="passwordchange",d.showLogin=!1,t("loginContent")):d.retrievePasswordRecoveryEnabled(),0<document.querySelectorAll("*:autofill").length&&document.querySelectorAll("*:autofill").forEach(e=>{e.parentElement.classList.add("md-input-has-value")})},100)},this.login=function(){return d.loginState="authenticating",n.login(d.creds).then(function(e){e.totpmissingkey?(d.loginState="totpcode",i("totpcode")):e.totpdisabled?(d.loginState="totpdisabled",d.cn=e.cn,d.url=e.url):(d.loginState="logged",d.cn=e.cn,d.url=e.url,s(function(){d.continueLogin()},1e3))},function(e){var o,s;d.loginState="error",e.error?d.errorMessage=e.error:0<e.grace?(d.loginState="passwordwillexpire",d.cn=e.cn,d.url=e.url,d.errorMessage=l("You have %{0} logins remaining before your account is locked. Please change your password in the preference dialog.",e.grace)):0<e.expire?(s=86400<e.expire?(o=Math.round(e.expire/86400),l("days")):3600<e.expire?(o=Math.round(e.expire/3600),l("hours")):60<e.expire?(o=Math.round(e.expire/60),l("minutes")):(o=e.expire,l("seconds")),d.loginState="passwordwillexpire",d.cn=e.cn,d.url=e.url,d.errorMessage=l("Your password is going to expire in %{0} %{1}.",o,s)):e.passwordexpired&&(d.loginState="passwordchange",d.url=e.url)}),!1},this.restoreLogin=function(){d.showLogin=!1,"SecretQuestion"===d.passwordRecovery.passwordRecoveryMode?(t("loginContent"),d.passwordRecoveryInfo()):(delete d.creds.verificationCode,d.passwordRecoveryAbort())},this.continueLogin=function(){o.location.href===d.url?o.location.reload(!0):o.location.href=d.url},this.showAbout=function(e){function o(e){this.closeDialog=function(){e.hide()}}a.show({targetEvent:e,templateUrl:"aboutBox.html",controller:o,controllerAs:"about"}),o.$inject=["$mdDialog"]},this.changeLanguage=function(e){o.location.href=ApplicationBaseURL+"login?language="+this.creds.language},this.hello=function(e){return!1},this.canChangePassword=function(e){return this.passwords.newPasswordConfirmation&&this.passwords.newPasswordConfirmation.length&&this.passwords.newPassword!=this.passwords.newPasswordConfirmation?(e.newPasswordConfirmation.$setValidity("newPasswordMismatch",!1),!1):(e.newPasswordConfirmation.$setValidity("newPasswordMismatch",!0),!!(this.passwords.newPassword&&0<this.passwords.newPassword.length&&this.passwords.newPasswordConfirmation&&this.passwords.newPasswordConfirmation.length&&this.passwords.newPassword==this.passwords.newPasswordConfirmation&&(this.isInPasswordRecoveryMode()||!this.loginState&&this.passwords.oldPassword&&0<this.passwords.oldPassword.length||"passwordchange"==this.loginState&&this.passwords.oldPassword&&0<this.passwords.oldPassword.length)))},this.changePassword=function(){n.changePassword(this.creds.username,this.creds.domain,this.passwords.newPassword,this.passwords.oldPassword,this.passwordRecovery.passwordRecoveryToken).then(function(e){d.loginState="message",d.url=e.url,d.errorMessage=l("The password was changed successfully.")},function(e){d.loginState="error",d.errorMessage=e})},this.passwordRecoveryInfo=function(){d.loginState="passwordrecovery",d.passwordRecovery.showLoader=!0,n.passwordRecovery(this.creds.username,this.creds.domain).then(function(e){d.passwordRecovery.passwordRecoveryMode=e.mode,"SecretQuestion"===e.mode?(d.passwordRecovery.passwordRecoveryQuestion=e.secretQuestionLabel,d.passwordRecovery.passwordRecoveryQuestionKey=e.secretQuestion):"SecondaryEmail"===e.mode?d.passwordRecovery.passwordRecoverySecondaryEmailText=l("A link will be sent to %{0}",e.obfuscatedRecoveryEmail):"Disabled"===e.mode&&(d.loginState="error",d.errorMessage=l("No password recovery method has been defined for this user")),d.passwoLine truncated
|
||||
!function(){"use strict";angular.module("SOGo.MainUI",["SOGo.Common","SOGo.Authentication"]);function e(e,s,o,r,a,i,n,t,d,c){var w=this;this.$onInit=function(){this.creds={username:s.cookieUsername,password:null,domain:null,rememberLogin:angular.isDefined(s.cookieUsername)&&0<s.cookieUsername.length},/\blanguage=/.test(s.location.search)&&(this.creds.language=s.language),this.loginState=!1,this.verificationCodePattern="\\d{6}",this.passwords={newPassword:null,newPasswordConfirmation:null,oldPassword:null,visible:!1},this.passwordRecovery={passwordRecoveryEnabled:!1,passwordRecoveryQuestionKey:null,passwordRecoveryQuestion:null,passwordRecoveryMode:null,passwordRecoveryQuestionAnswer:null,passwordRecoveryToken:null,passwordRecoveryLinkTimer:null,passwordRecoverySecondaryEmailText:null,passwordRecoveryMailDomain:null,showLoader:!1},this.showLogin=!1,o(function(){w.showLogin=!0;var e=window.location.search;let s=new URLSearchParams(e).get("token");0<window.location.pathname.indexOf("passwordRecoveryEmail")&&s?3===(e=(s=s.replace(/\//g,"")).split(".")).length&&(w.passwordRecovery.passwordRecoveryToken=s,e=JSON.parse(atob(e[1])),w.creds.username=e.username,w.creds.domain=e.domain,w.passwordRecovery.passwordRecoveryToken=s,w.passwordRecovery.passwordRecoveryMode="SecondaryEmail",w.passwordRecovery.passwordRecoveryEnabled=!0,w.loginState="passwordchange",t("loginContent")):w.retrievePasswordRecoveryEnabled(),0<document.querySelectorAll("*:autofill").length&&document.querySelectorAll("*:autofill").forEach(e=>{e.parentElement.classList.add("md-input-has-value")})},100)},this.login=function(){return w.loginState="authenticating",i.login(w.creds).then(function(e){e.totpmissingkey?(w.loginState="totpcode",n("totpcode")):e.totpdisabled?(w.loginState="totpdisabled",w.cn=e.cn,w.url=e.url):(w.loginState="logged",w.cn=e.cn,w.url=e.url,o(function(){w.continueLogin()},1e3))},function(e){var s,o;w.loginState="error",e.error?w.errorMessage=e.error:0<e.grace?(w.loginState="passwordwillexpire",w.cn=e.cn,w.url=e.url,w.passwordPolicy=e.userPolicies||[],w.errorMessage=l("You have %{0} logins remaining before your account is locked. Please change your password in the preference dialog.",e.grace)):0<e.expire?(o=86400<e.expire?(s=Math.round(e.expire/86400),l("days")):3600<e.expire?(s=Math.round(e.expire/3600),l("hours")):60<e.expire?(s=Math.round(e.expire/60),l("minutes")):(s=e.expire,l("seconds")),w.loginState="passwordwillexpire",w.cn=e.cn,w.url=e.url,w.errorMessage=l("Your password is going to expire in %{0} %{1}.",s,o)):(e.passwordexpired&&2==e.passwordexpired||e.passwordexpired)&&(w.loginState="passwordchange",w.passwordPolicy=e.userPolicies||[],w.url=e.url,w.passwordexpired=e.passwordexpired)}),!1},this.restoreLogin=function(){"SecretQuestion"===w.passwordRecovery.passwordRecoveryMode?t("loginContent"):delete w.creds.verificationCode,w.passwordRecoveryAbort()},this.continueLogin=function(){s.location.href===w.url?s.location.reload(!0):s.location.href=w.url},this.showAbout=function(e){function s(e){this.closeDialog=function(){e.hide()}}a.show({targetEvent:e,templateUrl:"aboutBox.html",controller:s,controllerAs:"about"}),s.$inject=["$mdDialog"]},this.changeLanguage=function(e){s.location.href=ApplicationBaseURL+"changeLanguage?language="+this.creds.language},this.canChangePassword=function(e){return this.passwords.newPasswordConfirmation&&this.passwords.newPasswordConfirmation.length&&this.passwords.newPassword!=this.passwords.newPasswordConfirmation?(e.newPasswordConfirmation.$setValidity("newPasswordMismatch",!1),!1):(e.newPasswordConfirmation.$setValidity("newPasswordMismatch",!0),!!(this.passwords.newPassword&&0<this.passwords.newPassword.length&&this.passwords.newPasswordConfirmation&&this.passwords.newPasswordConfirmation.length&&this.passwords.newPassword==this.passwords.newPasswordConfirmation&&(this.isInPasswordRecoveryMode()||!this.loginState&&this.passwords.oldPassword&&0<this.passwords.oldPassword.length||"passwordchange"==this.loginState&&this.passwords.oldPassword&&0<this.passwords.oldPassword.length)))},this.changePassword=function(){i.changePassword(this.creds.username,this.creds.domain,this.passwords.newPassword,this.passwords.oldPassword,this.passwordRecovery.passwordRecoveryToken).then(function(e){w.loginState="message",w.url=e.url,w.errorMessage=l("The password was changed successfully.")},function(e){c.show(c.simple().textContent(e).position(d.toastPosition).hideDelay(2e3))})},this.passwordRecoveryInfo=function(){w.loginState="passwordrecovery",w.passwordRecovery.showLoader=!0,i.passwordRecovery(this.creds.username,this.creds.domain).then(function(e){w.passwordRecovery.passwordRecoveryMode=e.mode,"SecretQuestion"===e.mode?(w.passwordRecovery.passwordRecoveryQuestion=e.secretQuestionLabel,w.passwordRecovery.passwordRecoveryQuestionKey=e.secretQuestion):"SecondaryEmail"===e.mode?w.passwordRecovery.passwordRecoverySecondaryEmailText=l("A link will be sent to %{0}",e.obfuscatedRecoveryEmail):"Disabled"===e.mode&&(Line truncated
|
||||
//# sourceMappingURL=Main.js.map
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"Main.js","sources":["Main/Main.app.js"],"names":["angular","module","LoginController","$scope","$window","$timeout","Dialog","$mdDialog","Authentication","focus","rippleDo","vm","this","$onInit","creds","username","cookieUsername","password","domain","rememberLogin","isDefined","length","test","location","search","language","loginState","verificationCodePattern","passwords","newPassword","newPasswordConfirmation","oldPassword","visible","passwordRecovery","passwordRecoveryEnabled","passwordRecoveryQuestionKey","passwordRecoveryQuestion","passwordRecoveryMode","passwordRecoveryQuestionAnswer","passwordRecoveryToken","passwordRecoveryLinkTimer","passwordRecoverySecondaryEmailText","passwordRecoveryMailDomain","showLoader","showLogin","queryString","window","let","token","URLSearchParams","get","pathname","indexOf","tokenArray","replace","split","info","JSON","parse","atob","retrievePasswordRecoveryEnabled","document","querySelectorAll","forEach","el","parentElement","classList","add","login","then","data","totpmissingkey","totpdisabled","cn","url","continueLogin","msg","value","string","error","errorMessage","grace","l","expire","Math","round","passwordexpired","restoreLogin","passwordRecoveryInfo","verificationCode","passwordRecoveryAbort","href","reload","showAbout","$event","AboutDialogController","closeDialog","hide","show","targetEvent","templateUrl","controller","controllerAs","$inject","changeLanguage","ApplicationBaseURL","hello","form","canChangePassword","$setValidity","isInPasswordRecoveryMode","changePassword","mode","secretQuestionLabel","secretQuestion","obfuscatedRecoveryEmail","passwordRecoveryEmail","passwordRecoveryCheck","usernameChanged","clearTimeout","setTimeout","mailDomain","changePasswordVisibility","field","getElementById","type","innerHTML"],"mappings":"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 truncated
|
||||
{"version":3,"file":"Main.js","sources":["Main/Main.app.js"],"names":["angular","module","LoginController","$scope","$window","$timeout","Dialog","$mdDialog","Authentication","focus","rippleDo","sgConstant","$mdToast","vm","this","$onInit","creds","username","cookieUsername","password","domain","rememberLogin","isDefined","length","test","location","search","language","loginState","verificationCodePattern","passwords","newPassword","newPasswordConfirmation","oldPassword","visible","passwordRecovery","passwordRecoveryEnabled","passwordRecoveryQuestionKey","passwordRecoveryQuestion","passwordRecoveryMode","passwordRecoveryQuestionAnswer","passwordRecoveryToken","passwordRecoveryLinkTimer","passwordRecoverySecondaryEmailText","passwordRecoveryMailDomain","showLoader","showLogin","queryString","window","let","token","URLSearchParams","get","pathname","indexOf","tokenArray","replace","split","info","JSON","parse","atob","retrievePasswordRecoveryEnabled","document","querySelectorAll","forEach","el","parentElement","classList","add","login","then","data","totpmissingkey","totpdisabled","cn","url","continueLogin","msg","value","string","error","errorMessage","grace","passwordPolicy","userPolicies","l","expire","Math","round","passwordexpired","restoreLogin","verificationCode","passwordRecoveryAbort","href","reload","showAbout","$event","AboutDialogController","closeDialog","hide","show","targetEvent","templateUrl","controller","controllerAs","$inject","changeLanguage","ApplicationBaseURL","canChangePassword","form","$setValidity","isInPasswordRecoveryMode","changePassword","simple","textContent","position","toastPosition","hideDelay","passwordRecoveryInfo","mode","secretQuestionLabel","secretQuestion","obfuscatedRecoveryEmail","passwordRecoveryEmail","passwordRecoveryCheck","isPasswordExpiredSecurity","usernameChanged","clearTimeout","setTimeout","mailDomain","changePasswordVisibility","field","getElementById","type","innerHTML"],"mappings":"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 truncated
|
||||
@@ -1,6 +1,6 @@
|
||||
/* -*- Mode: javascript; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* JavaScript for MainUI (SOGoRootPage) */
|
||||
(function() {
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
angular.module('SOGo.MainUI', ['SOGo.Common', 'SOGo.Authentication']);
|
||||
@@ -9,8 +9,8 @@
|
||||
/**
|
||||
* @ngInject
|
||||
*/
|
||||
LoginController.$inject = ['$scope', '$window', '$timeout', 'Dialog', '$mdDialog', 'Authentication', 'sgFocus', 'sgRippleClick'];
|
||||
function LoginController($scope, $window, $timeout, Dialog, $mdDialog, Authentication, focus, rippleDo) {
|
||||
LoginController.$inject = ['$scope', '$window', '$timeout', 'Dialog', '$mdDialog', 'Authentication', 'sgFocus', 'sgRippleClick', 'sgConstant', '$mdToast'];
|
||||
function LoginController($scope, $window, $timeout, Dialog, $mdDialog, Authentication, focus, rippleDo, sgConstant, $mdToast) {
|
||||
var vm = this;
|
||||
|
||||
this.$onInit = function () {
|
||||
@@ -20,6 +20,8 @@
|
||||
domain: null,
|
||||
rememberLogin: angular.isDefined($window.cookieUsername) && $window.cookieUsername.length > 0
|
||||
};
|
||||
if($window.loginHint)
|
||||
this.creds.username = $window.loginHint;
|
||||
// Send selected language only if user has changed it
|
||||
if (/\blanguage=/.test($window.location.search))
|
||||
this.creds.language = $window.language;
|
||||
@@ -69,10 +71,9 @@
|
||||
vm.passwordRecovery.passwordRecoveryEnabled = true;
|
||||
|
||||
vm.loginState = 'passwordchange';
|
||||
vm.showLogin = false;
|
||||
rippleDo('loginContent');
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
vm.retrievePasswordRecoveryEnabled();
|
||||
}
|
||||
@@ -83,14 +84,14 @@
|
||||
el.parentElement.classList.add("md-input-has-value");
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
|
||||
}, 100);
|
||||
|
||||
};
|
||||
|
||||
this.login = function() {
|
||||
this.login = function () {
|
||||
vm.loginState = 'authenticating';
|
||||
Authentication.login(vm.creds)
|
||||
.then(function(data) {
|
||||
.then(function (data) {
|
||||
|
||||
if (data.totpmissingkey) {
|
||||
vm.loginState = 'totpcode';
|
||||
@@ -107,10 +108,76 @@
|
||||
vm.url = data.url;
|
||||
|
||||
// Let the user see the succesfull message before reloading the page
|
||||
$timeout(function() {
|
||||
$timeout(function () {
|
||||
vm.continueLogin();
|
||||
}, 1000);
|
||||
}
|
||||
}, function (msg) {
|
||||
vm.loginState = 'error';
|
||||
|
||||
if (msg.error) {
|
||||
vm.errorMessage = msg.error;
|
||||
}
|
||||
else if (msg.grace > 0) {
|
||||
// Password is expired, grace logins limit is not yet reached
|
||||
vm.loginState = 'passwordwillexpire';
|
||||
vm.cn = msg.cn;
|
||||
vm.url = msg.url;
|
||||
vm.passwordPolicy = msg.userPolicies ? msg.userPolicies : [];
|
||||
vm.errorMessage = l('You have %{0} logins remaining before your account is locked. Please change your password in the preference dialog.', msg.grace);
|
||||
}
|
||||
else if (msg.expire > 0) {
|
||||
// Password will soon expire
|
||||
var value, string;
|
||||
if (msg.expire > 86400) {
|
||||
value = Math.round(msg.expire / 86400);
|
||||
string = l("days");
|
||||
}
|
||||
else if (msg.expire > 3600) {
|
||||
value = Math.round(msg.expire / 3600);
|
||||
string = l("hours");
|
||||
}
|
||||
else if (msg.expire > 60) {
|
||||
value = Math.round(msg.expire / 60);
|
||||
string = l("minutes");
|
||||
}
|
||||
else {
|
||||
value = msg.expire;
|
||||
string = l("seconds");
|
||||
}
|
||||
vm.loginState = 'passwordwillexpire';
|
||||
vm.cn = msg.cn;
|
||||
vm.url = msg.url;
|
||||
vm.errorMessage = l('Your password is going to expire in %{0} %{1}.', value, string);
|
||||
}
|
||||
else if (msg.passwordexpired && msg.passwordexpired == 2) {
|
||||
vm.loginState = 'passwordchange';
|
||||
vm.passwordPolicy = msg.userPolicies ? msg.userPolicies : [];
|
||||
vm.url = msg.url;
|
||||
vm.passwordexpired = msg.passwordexpired;
|
||||
} else if (msg.passwordexpired) {
|
||||
vm.loginState = 'passwordchange';
|
||||
vm.passwordPolicy = msg.userPolicies ? msg.userPolicies : [];
|
||||
vm.url = msg.url;
|
||||
vm.passwordexpired = msg.passwordexpired;
|
||||
}
|
||||
|
||||
});
|
||||
return false;
|
||||
};
|
||||
|
||||
this.loginName = function() {
|
||||
vm.loginState = 'authenticating';
|
||||
Authentication.loginName(vm.creds)
|
||||
.then(function(data) {
|
||||
vm.loginState = 'logged';
|
||||
vm.cn = data.cn;
|
||||
vm.url = data.url;
|
||||
|
||||
// Let the user see the succesfull message before reloading the page
|
||||
$timeout(function() {
|
||||
vm.continueLogin();
|
||||
}, 1000);
|
||||
}, function(msg) {
|
||||
vm.loginState = 'error';
|
||||
|
||||
@@ -157,25 +224,24 @@
|
||||
return false;
|
||||
};
|
||||
|
||||
this.restoreLogin = function() {
|
||||
vm.showLogin = false;
|
||||
this.restoreLogin = function () {
|
||||
if ('SecretQuestion' === vm.passwordRecovery.passwordRecoveryMode) {
|
||||
rippleDo('loginContent');
|
||||
vm.passwordRecoveryInfo();
|
||||
vm.passwordRecoveryAbort();
|
||||
} else {
|
||||
delete vm.creds.verificationCode;
|
||||
vm.passwordRecoveryAbort();
|
||||
}
|
||||
};
|
||||
|
||||
this.continueLogin = function() {
|
||||
this.continueLogin = function () {
|
||||
if ($window.location.href === vm.url)
|
||||
$window.location.reload(true);
|
||||
else
|
||||
$window.location.href = vm.url;
|
||||
};
|
||||
|
||||
this.showAbout = function($event) {
|
||||
this.showAbout = function ($event) {
|
||||
$mdDialog.show({
|
||||
targetEvent: $event,
|
||||
templateUrl: 'aboutBox.html',
|
||||
@@ -184,24 +250,20 @@
|
||||
});
|
||||
AboutDialogController.$inject = ['$mdDialog'];
|
||||
function AboutDialogController($mdDialog) {
|
||||
this.closeDialog = function() {
|
||||
this.closeDialog = function () {
|
||||
$mdDialog.hide();
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
this.changeLanguage = function($event) {
|
||||
this.changeLanguage = function ($event) {
|
||||
// Reload page
|
||||
$window.location.href = ApplicationBaseURL + 'login?language=' + this.creds.language;
|
||||
$window.location.href = ApplicationBaseURL + 'changeLanguage?language=' + this.creds.language;
|
||||
};
|
||||
|
||||
this.hello = function (form) {
|
||||
return !true;
|
||||
}
|
||||
|
||||
this.canChangePassword = function(form) {
|
||||
this.canChangePassword = function (form) {
|
||||
if (this.passwords.newPasswordConfirmation && this.passwords.newPasswordConfirmation.length &&
|
||||
this.passwords.newPassword != this.passwords.newPasswordConfirmation) {
|
||||
this.passwords.newPassword != this.passwords.newPasswordConfirmation) {
|
||||
form.newPasswordConfirmation.$setValidity('newPasswordMismatch', false);
|
||||
return false;
|
||||
}
|
||||
@@ -209,25 +271,29 @@
|
||||
form.newPasswordConfirmation.$setValidity('newPasswordMismatch', true);
|
||||
}
|
||||
if (this.passwords.newPassword && this.passwords.newPassword.length > 0 &&
|
||||
this.passwords.newPasswordConfirmation && this.passwords.newPasswordConfirmation.length &&
|
||||
this.passwords.newPassword == this.passwords.newPasswordConfirmation &&
|
||||
((this.isInPasswordRecoveryMode()) ||
|
||||
(!this.loginState && this.passwords.oldPassword && this.passwords.oldPassword.length > 0) ||
|
||||
this.passwords.newPasswordConfirmation && this.passwords.newPasswordConfirmation.length &&
|
||||
this.passwords.newPassword == this.passwords.newPasswordConfirmation &&
|
||||
((this.isInPasswordRecoveryMode()) ||
|
||||
(!this.loginState && this.passwords.oldPassword && this.passwords.oldPassword.length > 0) ||
|
||||
('passwordchange' == this.loginState && this.passwords.oldPassword && this.passwords.oldPassword.length > 0)
|
||||
))
|
||||
))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
this.changePassword = function() {
|
||||
Authentication.changePassword(this.creds.username, this.creds.domain, this.passwords.newPassword, this.passwords.oldPassword, this.passwordRecovery.passwordRecoveryToken).then(function(data) {
|
||||
this.changePassword = function () {
|
||||
Authentication.changePassword(this.creds.username, this.creds.domain, this.passwords.newPassword, this.passwords.oldPassword, this.passwordRecovery.passwordRecoveryToken).then(function (data) {
|
||||
vm.loginState = 'message';
|
||||
vm.url = data.url;
|
||||
vm.errorMessage = l('The password was changed successfully.');
|
||||
}, function(msg) {
|
||||
vm.loginState = 'error';
|
||||
vm.errorMessage = msg;
|
||||
}, function (msg) {
|
||||
$mdToast.show(
|
||||
$mdToast.simple()
|
||||
.textContent(msg)
|
||||
.position(sgConstant.toastPosition)
|
||||
.hideDelay(2000)
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -260,38 +326,42 @@
|
||||
, this.passwordRecovery.passwordRecoveryMailDomain).then(function () {
|
||||
vm.loginState = 'sendrecoverymail';
|
||||
vm.passwordRecovery.showLoader = false;
|
||||
}, function (msg) {
|
||||
vm.loginState = 'error';
|
||||
vm.errorMessage = msg;
|
||||
}, function (msg) {
|
||||
vm.loginState = 'error';
|
||||
vm.errorMessage = msg;
|
||||
vm.passwordRecovery.showLoader = false;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this.passwordRecoveryCheck = function () {
|
||||
vm.passwordRecovery.showLoader = true;
|
||||
Authentication.passwordRecoveryCheck(this.creds.username, this.creds.domain
|
||||
, this.passwordRecovery.passwordRecoveryMode
|
||||
, this.passwordRecovery.passwordRecoveryQuestionKey
|
||||
, this.passwordRecovery.passwordRecoveryQuestionAnswer
|
||||
, this.passwordRecovery.passwordRecoveryMailDomain).then(function (token) {
|
||||
if ("SecretQuestion" == vm.passwordRecovery.passwordRecoveryMode) {
|
||||
vm.passwordRecovery.passwordRecoveryToken = token;
|
||||
vm.loginState = 'passwordchange';
|
||||
} else if ("SecondaryEmail" == vm.passwordRecovery.passwordRecoveryMode) {
|
||||
vm.loginState = 'sendrecoverymail';
|
||||
}
|
||||
vm.passwordRecovery.showLoader = false;
|
||||
}, function (msg) {
|
||||
vm.loginState = 'error';
|
||||
vm.errorMessage = msg;
|
||||
vm.passwordRecovery.showLoader = false;
|
||||
});
|
||||
, this.passwordRecovery.passwordRecoveryMode
|
||||
, this.passwordRecovery.passwordRecoveryQuestionKey
|
||||
, this.passwordRecovery.passwordRecoveryQuestionAnswer
|
||||
, this.passwordRecovery.passwordRecoveryMailDomain).then(function (token) {
|
||||
if ("SecretQuestion" == vm.passwordRecovery.passwordRecoveryMode) {
|
||||
vm.passwordRecovery.passwordRecoveryToken = token;
|
||||
vm.loginState = 'passwordchange';
|
||||
} else if ("SecondaryEmail" == vm.passwordRecovery.passwordRecoveryMode) {
|
||||
vm.loginState = 'sendrecoverymail';
|
||||
}
|
||||
vm.passwordRecovery.showLoader = false;
|
||||
}, function (msg) {
|
||||
vm.loginState = 'error';
|
||||
vm.errorMessage = msg;
|
||||
vm.passwordRecovery.showLoader = false;
|
||||
});
|
||||
};
|
||||
|
||||
this.isPasswordExpiredSecurity = function () {
|
||||
return (this.passwordexpired && 2 === this.passwordexpired);
|
||||
};
|
||||
|
||||
this.isInPasswordRecoveryMode = function () {
|
||||
return (("SecretQuestion" == this.passwordRecovery.passwordRecoveryMode ||
|
||||
"SecondaryEmail" == this.passwordRecovery.passwordRecoveryMode) &&
|
||||
this.passwordRecovery.passwordRecoveryToken) ? true : false;
|
||||
return (("SecretQuestion" == this.passwordRecovery.passwordRecoveryMode) ||
|
||||
("SecondaryEmail" == this.passwordRecovery.passwordRecoveryMode &&
|
||||
this.passwordRecovery.passwordRecoveryToken)) ? true : false;
|
||||
};
|
||||
|
||||
this.passwordRecoveryAbort = function () {
|
||||
|
||||
Loaded 100 of 186 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user