2022-05-05 13:23:05 +00:00
// (c) Copyright Ascensio System SIA 2010-2022
//
// This program is a free software product.
// You can redistribute it and/or modify it under the terms
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
// any third-party rights.
//
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
//
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
//
// The interactive user interfaces in modified source and object code versions of the Program must
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
//
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
// trademark law for use of our trademarks.
//
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
using Constants = ASC . Core . Users . Constants ;
namespace ASC.ActiveDirectory.Base ;
[Scope]
public class LdapUserImporter : IDisposable
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
public List < LdapObject > AllDomainUsers { get ; private set ; }
public List < LdapObject > AllDomainGroups { get ; private set ; }
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
public Dictionary < LdapObject , LdapSettingsStatus > AllSkipedDomainUsers { get ; private set ; }
public Dictionary < LdapObject , LdapSettingsStatus > AllSkipedDomainGroups { get ; private set ; }
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
private string _ldapDomain ;
private readonly string _unknownDomain ;
public string LDAPDomain
{
get
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
if ( ! string . IsNullOrEmpty ( _ldapDomain ) )
return _ldapDomain ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
_ldapDomain = LoadLDAPDomain ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( string . IsNullOrEmpty ( _ldapDomain ) )
{
_ldapDomain = _unknownDomain ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
return _ldapDomain ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
}
public List < string > PrimaryGroupIds { get ; set ; }
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
public LdapSettings Settings
{
get { return LdapHelper . Settings ; }
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
public LdapHelper LdapHelper { get ; private set ; }
public LdapLocalization Resource { get ; private set ; }
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
private List < string > _watchedNestedGroups ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
private readonly ILog _log ;
private readonly LdapObjectExtension _ldapObjectExtension ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
private UserManager UserManager { get ; set ; }
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
public LdapUserImporter (
IOptionsMonitor < ILog > option ,
UserManager userManager ,
IConfiguration configuration ,
NovellLdapHelper novellLdapHelper ,
LdapObjectExtension ldapObjectExtension )
{
_unknownDomain = configuration [ "ldap:domain" ] ? ? "LDAP" ;
AllDomainUsers = new List < LdapObject > ( ) ;
AllDomainGroups = new List < LdapObject > ( ) ;
AllSkipedDomainUsers = new Dictionary < LdapObject , LdapSettingsStatus > ( ) ;
AllSkipedDomainGroups = new Dictionary < LdapObject , LdapSettingsStatus > ( ) ;
LdapHelper = novellLdapHelper ;
_log = option . Get ( "ASC" ) ;
UserManager = userManager ;
_watchedNestedGroups = new List < string > ( ) ;
_ldapObjectExtension = ldapObjectExtension ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
public void Init ( LdapSettings settings , LdapLocalization resource )
{
2022-04-11 09:51:54 +00:00
( ( NovellLdapHelper ) LdapHelper ) . Init ( settings ) ;
2022-03-17 19:44:34 +00:00
Resource = resource ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
public List < UserInfo > GetDiscoveredUsersByAttributes ( )
{
var users = new List < UserInfo > ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! AllDomainUsers . Any ( ) & & ! TryLoadLDAPUsers ( ) )
return users ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var usersToAdd = AllDomainUsers . Select ( ldapObject = > _ldapObjectExtension . ToUserInfo ( ldapObject , this , _log ) ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
users . AddRange ( usersToAdd ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
return users ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
public List < GroupInfo > GetDiscoveredGroupsByAttributes ( )
{
if ( ! Settings . GroupMembership )
return new List < GroupInfo > ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! AllDomainGroups . Any ( ) & & ! TryLoadLDAPGroups ( ) )
return new List < GroupInfo > ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var groups = new List < GroupInfo > ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var groupsToAdd = AllDomainGroups . ConvertAll ( g = > LdapObjectExtension . ToGroupInfo ( g , Settings ) ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
groups . AddRange ( groupsToAdd ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
return groups ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
public List < UserInfo > GetGroupUsers ( GroupInfo groupInfo )
{
return GetGroupUsers ( groupInfo , true ) ;
}
private List < UserInfo > GetGroupUsers ( GroupInfo groupInfo , bool clearCache )
{
if ( ! LdapHelper . IsConnected )
LdapHelper . Connect ( ) ;
_log . DebugFormat ( "LdapUserImporter.GetGroupUsers(Group name: {0})" , groupInfo . Name ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var users = new List < UserInfo > ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! AllDomainGroups . Any ( ) & & ! TryLoadLDAPGroups ( ) )
return users ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var domainGroup = AllDomainGroups . FirstOrDefault ( lg = > lg . Sid . Equals ( groupInfo . Sid ) ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( domainGroup = = null )
return users ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var members = LdapObjectExtension . GetAttributes ( domainGroup , Settings . GroupAttribute , _log ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
foreach ( var member in members )
{
var ldapUser = FindUserByMember ( member ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ldapUser = = null )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
var nestedLdapGroup = FindGroupByMember ( member ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( nestedLdapGroup ! = null )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
_log . DebugFormat ( "Found nested LDAP Group: {0}" , nestedLdapGroup . DistinguishedName ) ;
if ( clearCache )
_watchedNestedGroups = new List < string > ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( _watchedNestedGroups . Contains ( nestedLdapGroup . DistinguishedName ) )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
_log . DebugFormat ( "Skip already watched nested LDAP Group: {0}" , nestedLdapGroup . DistinguishedName ) ;
continue ;
}
_watchedNestedGroups . Add ( nestedLdapGroup . DistinguishedName ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var nestedGroupInfo = LdapObjectExtension . ToGroupInfo ( nestedLdapGroup , Settings , _log ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var nestedGroupUsers = GetGroupUsers ( nestedGroupInfo , false ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
foreach ( var groupUser in nestedGroupUsers )
{
if ( ! users . Exists ( u = > u . Sid = = groupUser . Sid ) )
users . Add ( groupUser ) ;
}
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
continue ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var userInfo = _ldapObjectExtension . ToUserInfo ( ldapUser , this , _log ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! users . Exists ( u = > u . Sid = = userInfo . Sid ) )
users . Add ( userInfo ) ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( PrimaryGroupIds ! = null & & PrimaryGroupIds . Any ( id = > domainGroup . Sid . EndsWith ( "-" + id ) ) )
{
// Domain Users found
var ldapUsers = FindUsersByPrimaryGroup ( domainGroup . Sid ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
foreach ( var ldapUser in ldapUsers )
{
2022-03-14 09:02:43 +00:00
var userInfo = _ldapObjectExtension . ToUserInfo ( ldapUser , this , _log ) ;
2022-03-08 05:37:20 +00:00
if ( ! users . Exists ( u = > u . Sid = = userInfo . Sid ) )
users . Add ( userInfo ) ;
}
2022-03-17 19:44:34 +00:00
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
return users ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
const string GROUP_MEMBERSHIP = "groupMembership" ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
private IEnumerable < LdapObject > GetLdapUserGroups ( LdapObject ldapUser )
{
var ldapUserGroups = new List < LdapObject > ( ) ;
try
{
if ( ! Settings . GroupMembership )
{
return ldapUserGroups ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
if ( ldapUser = = null | |
string . IsNullOrEmpty ( ldapUser . Sid ) )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
return ldapUserGroups ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! LdapHelper . IsConnected )
LdapHelper . Connect ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var userGroups = LdapObjectExtension . GetAttributes ( ldapUser , LdapConstants . ADSchemaAttributes . MEMBER_OF , _log )
. Select ( s = > LdapUtils . UnescapeLdapString ( s ) )
. ToList ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! userGroups . Any ( ) )
{
userGroups = LdapObjectExtension . GetAttributes ( ldapUser , GROUP_MEMBERSHIP , _log ) ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var searchExpressions = new List < Expression > ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var primaryGroupId = ldapUser . GetValue ( LdapConstants . ADSchemaAttributes . PRIMARY_GROUP_ID ) as string ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! string . IsNullOrEmpty ( primaryGroupId ) )
{
var userSid = ldapUser . Sid ;
var index = userSid . LastIndexOf ( "-" , StringComparison . InvariantCultureIgnoreCase ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( index > - 1 )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
var primaryGroupSid = userSid . Substring ( 0 , index + 1 ) + primaryGroupId ;
searchExpressions . Add ( Expression . Equal ( ldapUser . SidAttribute , primaryGroupSid ) ) ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( userGroups . Any ( ) )
{
var cnRegex = new Regex ( ",[A-z]{2}=" ) ;
searchExpressions . AddRange ( userGroups
. Select ( g = > g . Substring ( 0 , cnRegex . Match ( g ) . Index ) )
. Where ( s = > ! string . IsNullOrEmpty ( s ) )
. Select ( Expression . Parse )
. Where ( e = > e ! = null ) ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var criteria = Criteria . Any ( searchExpressions . ToArray ( ) ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var foundList = LdapHelper . GetGroups ( criteria ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( foundList . Any ( ) )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
ldapUserGroups . AddRange ( foundList ) ;
2022-03-08 05:37:20 +00:00
}
}
2022-03-17 19:44:34 +00:00
else
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
var ldapGroups = LdapHelper . GetGroups ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
ldapUserGroups . AddRange (
ldapGroups . Where (
ldapGroup = >
LdapHelper . UserExistsInGroup ( ldapGroup , ldapUser , Settings ) ) ) ;
}
}
catch ( Exception ex )
{
if ( ldapUser ! = null )
_log . ErrorFormat ( "IsUserExistInGroups(login: '{0}' sid: '{1}') error {2}" ,
ldapUser . DistinguishedName , ldapUser . Sid , ex ) ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
return ldapUserGroups ;
}
public IEnumerable < GroupInfo > GetAndCheckCurrentGroups ( LdapObject ldapUser , IEnumerable < GroupInfo > portalGroups )
{
var result = new List < GroupInfo > ( ) ;
try
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
var searchExpressions = new List < Expression > ( ) ;
if ( portalGroups ! = null & & portalGroups . Any ( ) )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
searchExpressions . AddRange ( portalGroups . Select ( g = > Expression . Equal ( LdapConstants . ADSchemaAttributes . OBJECT_SID , g . Sid ) ) ) ;
}
else
{
return result ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var criteria = Criteria . Any ( searchExpressions . ToArray ( ) ) ;
var foundList = LdapHelper . GetGroups ( criteria ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( foundList . Any ( ) )
{
var stillExistingGroups = portalGroups . Where ( g = > foundList . Any ( fg = > fg . Sid = = g . Sid ) ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
foreach ( var group in stillExistingGroups )
{
if ( GetGroupUsers ( group ) . Any ( u = > u . Sid = = ldapUser . Sid ) )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
result . Add ( group ) ;
2022-03-08 05:37:20 +00:00
}
}
}
}
2022-03-17 19:44:34 +00:00
catch ( Exception ex )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
if ( ldapUser ! = null )
_log . ErrorFormat ( "GetAndCheckCurrentGroups(login: '{0}' sid: '{1}') error {2}" ,
ldapUser . DistinguishedName , ldapUser . Sid , ex ) ;
}
return result ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
public bool TrySyncUserGroupMembership ( Tuple < UserInfo , LdapObject > ldapUserInfo )
{
if ( ldapUserInfo = = null | |
! Settings . GroupMembership )
{
return false ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var userInfo = ldapUserInfo . Item1 ;
var ldapUser = ldapUserInfo . Item2 ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var portalUserLdapGroups =
2022-05-05 13:23:05 +00:00
UserManager . GetUserGroups ( userInfo . Id , IncludeType . All )
2022-03-17 19:44:34 +00:00
. Where ( g = > ! string . IsNullOrEmpty ( g . Sid ) )
. ToList ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
List < LdapObject > ldapUserGroupList = new List < LdapObject > ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
ldapUserGroupList . AddRange ( GetLdapUserGroups ( ldapUser ) ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! LdapHelper . IsConnected )
LdapHelper . Connect ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var actualPortalLdapGroups = GetAndCheckCurrentGroups ( ldapUser , portalUserLdapGroups ) . ToList ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
foreach ( var ldapUserGroup in ldapUserGroupList )
{
var groupInfo = UserManager . GetGroupInfoBySid ( ldapUserGroup . Sid ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( Equals ( groupInfo , Constants . LostGroupInfo ) )
{
_log . DebugFormat ( "TrySyncUserGroupMembership(groupname: '{0}' sid: '{1}') no portal group found, creating" , ldapUserGroup . DistinguishedName , ldapUserGroup . Sid ) ;
groupInfo = UserManager . SaveGroupInfo ( LdapObjectExtension . ToGroupInfo ( ldapUserGroup , Settings , _log ) ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
_log . DebugFormat ( "TrySyncUserGroupMembership(username: '{0}' sid: '{1}') adding user to group (groupname: '{2}' sid: '{3}')" , userInfo . UserName , ldapUser . Sid , groupInfo . Name , groupInfo . Sid ) ;
2022-05-05 13:23:05 +00:00
UserManager . AddUserIntoGroup ( userInfo . Id , groupInfo . ID ) ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
else if ( ! portalUserLdapGroups . Contains ( groupInfo ) )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
_log . DebugFormat ( "TrySyncUserGroupMembership(username: '{0}' sid: '{1}') adding user to group (groupname: '{2}' sid: '{3}')" , userInfo . UserName , ldapUser . Sid , groupInfo . Name , groupInfo . Sid ) ;
2022-05-05 13:23:05 +00:00
UserManager . AddUserIntoGroup ( userInfo . Id , groupInfo . ID ) ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
actualPortalLdapGroups . Add ( groupInfo ) ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
foreach ( var portalUserLdapGroup in portalUserLdapGroups )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
if ( ! actualPortalLdapGroups . Contains ( portalUserLdapGroup ) )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
_log . DebugFormat ( "TrySyncUserGroupMembership(username: '{0}' sid: '{1}') removing user from group (groupname: '{2}' sid: '{3}')" , userInfo . UserName , ldapUser . Sid , portalUserLdapGroup . Name , portalUserLdapGroup . Sid ) ;
2022-05-05 13:23:05 +00:00
UserManager . RemoveUserFromGroup ( userInfo . Id , portalUserLdapGroup . ID ) ;
2022-03-17 19:44:34 +00:00
}
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
return actualPortalLdapGroups . Count ! = 0 ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
public bool TryLoadLDAPUsers ( )
{
try
{
if ( ! Settings . EnableLdapAuthentication )
return false ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! LdapHelper . IsConnected )
LdapHelper . Connect ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var users = LdapHelper . GetUsers ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
foreach ( var user in users )
{
if ( string . IsNullOrEmpty ( user . Sid ) )
{
AllSkipedDomainUsers . Add ( user , LdapSettingsStatus . WrongSidAttribute ) ;
continue ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! CheckLoginAttribute ( user , Settings . LoginAttribute ) )
{
AllSkipedDomainUsers . Add ( user , LdapSettingsStatus . WrongLoginAttribute ) ;
continue ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! Settings . GroupMembership )
{
2022-03-08 05:37:20 +00:00
AllDomainUsers . Add ( user ) ;
2022-03-17 19:44:34 +00:00
continue ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
if ( ! Settings . UserAttribute . Equals ( LdapConstants . RfcLDAPAttributes . DN ,
StringComparison . InvariantCultureIgnoreCase ) & & ! CheckUserAttribute ( user , Settings . UserAttribute ) )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
AllSkipedDomainUsers . Add ( user , LdapSettingsStatus . WrongUserAttribute ) ;
continue ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
AllDomainUsers . Add ( user ) ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
if ( AllDomainUsers . Any ( ) )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
PrimaryGroupIds = AllDomainUsers . Select ( u = > u . GetValue ( LdapConstants . ADSchemaAttributes . PRIMARY_GROUP_ID ) ) . Cast < string > ( )
. Distinct ( ) . ToList ( ) ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
return AllDomainUsers . Any ( ) | | ! users . Any ( ) ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
catch ( ArgumentException )
{
_log . ErrorFormat ( "TryLoadLDAPUsers(): Incorrect filter. userFilter = {0}" , Settings . UserFilter ) ;
}
return false ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
public bool TryLoadLDAPGroups ( )
{
try
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
if ( ! Settings . EnableLdapAuthentication | | ! Settings . GroupMembership )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
return false ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! LdapHelper . IsConnected )
LdapHelper . Connect ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var groups = LdapHelper . GetGroups ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
foreach ( var group in groups )
{
if ( string . IsNullOrEmpty ( group . Sid ) )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
AllSkipedDomainGroups . Add ( group , LdapSettingsStatus . WrongSidAttribute ) ;
continue ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! CheckGroupAttribute ( group , Settings . GroupAttribute ) )
{
AllSkipedDomainGroups . Add ( group , LdapSettingsStatus . WrongGroupAttribute ) ;
continue ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! CheckGroupNameAttribute ( group , Settings . GroupNameAttribute ) )
{
AllSkipedDomainGroups . Add ( group , LdapSettingsStatus . WrongGroupNameAttribute ) ;
continue ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
AllDomainGroups . Add ( group ) ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
return AllDomainGroups . Any ( ) | | ! groups . Any ( ) ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
catch ( ArgumentException )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
_log . ErrorFormat ( "TryLoadLDAPGroups(): Incorrect group filter. groupFilter = {0}" , Settings . GroupFilter ) ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
return false ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
private string LoadLDAPDomain ( )
{
try
{
if ( ! Settings . EnableLdapAuthentication )
return null ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! LdapHelper . IsConnected )
LdapHelper . Connect ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
string ldapDomain ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( AllDomainUsers . Any ( ) )
{
ldapDomain = LdapObjectExtension . GetDomainFromDn ( AllDomainUsers . First ( ) ) ;
2022-03-08 05:37:20 +00:00
if ( ! string . IsNullOrEmpty ( ldapDomain ) )
return ldapDomain ;
2022-03-17 19:44:34 +00:00
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
ldapDomain = LdapHelper . SearchDomain ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! string . IsNullOrEmpty ( ldapDomain ) )
return ldapDomain ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
ldapDomain = LdapUtils . DistinguishedNameToDomain ( Settings . UserDN ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! string . IsNullOrEmpty ( ldapDomain ) )
return ldapDomain ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
ldapDomain = LdapUtils . DistinguishedNameToDomain ( Settings . GroupDN ) ;
if ( ! string . IsNullOrEmpty ( ldapDomain ) )
return ldapDomain ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
catch ( Exception ex )
{
_log . ErrorFormat ( "LoadLDAPDomain(): Error: {0}" , ex ) ;
}
return null ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
protected bool CheckLoginAttribute ( LdapObject user , string loginAttribute )
{
try
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
var member = user . GetValue ( loginAttribute ) ;
if ( member = = null | | string . IsNullOrWhiteSpace ( member . ToString ( ) ) )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
_log . DebugFormat ( "Login Attribute parameter ({0}) not found: DN = {1}" , Settings . LoginAttribute ,
user . DistinguishedName ) ;
2022-03-08 05:37:20 +00:00
return false ;
}
}
2022-03-17 19:44:34 +00:00
catch ( Exception e )
{
_log . ErrorFormat ( "Login Attribute parameter ({0}) not found: loginAttribute = {1}. {2}" , Settings . LoginAttribute ,
loginAttribute , e ) ;
return false ;
}
return true ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
protected bool CheckUserAttribute ( LdapObject user , string userAttr )
{
try
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
var userAttribute = user . GetValue ( userAttr ) ;
if ( userAttribute = = null | | string . IsNullOrWhiteSpace ( userAttribute . ToString ( ) ) )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
_log . DebugFormat ( "User Attribute parameter ({0}) not found: DN = {1}" , Settings . UserAttribute ,
user . DistinguishedName ) ;
2022-03-08 05:37:20 +00:00
return false ;
}
}
2022-03-17 19:44:34 +00:00
catch ( Exception e )
{
_log . ErrorFormat ( "User Attribute parameter ({0}) not found: userAttr = {1}. {2}" ,
Settings . UserAttribute , userAttr , e ) ;
return false ;
}
return true ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
protected bool CheckGroupAttribute ( LdapObject group , string groupAttr )
{
try
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
group . GetValue ( groupAttr ) ; // Group attribute can be empty - example => Domain users
}
catch ( Exception e )
{
_log . ErrorFormat ( "Group Attribute parameter ({0}) not found: {1}. {2}" ,
Settings . GroupAttribute , groupAttr , e ) ;
return false ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
return true ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
protected bool CheckGroupNameAttribute ( LdapObject group , string groupAttr )
{
try
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
var groupNameAttribute = group . GetValues ( groupAttr ) ;
if ( ! groupNameAttribute . Any ( ) )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
_log . DebugFormat ( "Group Name Attribute parameter ({0}) not found: {1}" , Settings . GroupNameAttribute ,
groupAttr ) ;
2022-03-08 05:37:20 +00:00
return false ;
}
}
2022-03-17 19:44:34 +00:00
catch ( Exception e )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
_log . ErrorFormat ( "Group Attribute parameter ({0}) not found: {1}. {2}" , Settings . GroupNameAttribute ,
groupAttr , e ) ;
return false ;
}
return true ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
private List < LdapObject > FindUsersByPrimaryGroup ( string sid )
{
_log . Debug ( "LdapUserImporter.FindUsersByPrimaryGroup()" ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! AllDomainUsers . Any ( ) & & ! TryLoadLDAPUsers ( ) )
return null ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
return
AllDomainUsers . Where (
lu = >
{
var primaryGroupId = lu . GetValue ( LdapConstants . ADSchemaAttributes . PRIMARY_GROUP_ID ) as string ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
return ! string . IsNullOrEmpty ( primaryGroupId ) & &
sid . EndsWith ( primaryGroupId ) ;
} )
. ToList ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
private LdapObject FindUserByMember ( string userAttributeValue )
{
if ( ! AllDomainUsers . Any ( ) & & ! TryLoadLDAPUsers ( ) )
return null ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
_log . DebugFormat ( "LdapUserImporter.FindUserByMember(user attr: {0})" , userAttributeValue ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
return AllDomainUsers . FirstOrDefault ( u = >
u . DistinguishedName . Equals ( userAttributeValue , StringComparison . InvariantCultureIgnoreCase )
| | Convert . ToString ( u . GetValue ( Settings . UserAttribute ) ) . Equals ( userAttributeValue ,
StringComparison . InvariantCultureIgnoreCase ) ) ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
private LdapObject FindGroupByMember ( string member )
{
if ( ! AllDomainGroups . Any ( ) & & ! TryLoadLDAPGroups ( ) )
return null ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
_log . DebugFormat ( "LdapUserImporter.FindGroupByMember(member: {0})" , member ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
return AllDomainGroups . FirstOrDefault ( g = >
g . DistinguishedName . Equals ( member , StringComparison . InvariantCultureIgnoreCase ) ) ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
public List < Tuple < UserInfo , LdapObject > > FindLdapUsers ( string login )
{
var listResults = new List < Tuple < UserInfo , LdapObject > > ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var ldapLogin = LdapLogin . ParseLogin ( login ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ldapLogin = = null )
return listResults ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! LdapHelper . IsConnected )
LdapHelper . Connect ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var exps = new List < Expression > { Expression . Equal ( Settings . LoginAttribute , ldapLogin . Username ) } ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! ldapLogin . Username . Equals ( login ) & & ldapLogin . ToString ( ) . Equals ( login ) )
{
exps . Add ( Expression . Equal ( Settings . LoginAttribute , login ) ) ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
string email = null ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! string . IsNullOrEmpty ( Settings . MailAttribute ) & & ! string . IsNullOrEmpty ( ldapLogin . Domain ) & & login . Contains ( "@" ) )
{
email = ldapLogin . ToString ( ) ;
exps . Add ( Expression . Equal ( Settings . MailAttribute , email ) ) ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var searchTerm = exps . Count > 1 ? Criteria . Any ( exps . ToArray ( ) ) . ToString ( ) : exps . First ( ) . ToString ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var users = LdapHelper . GetUsers ( searchTerm , ! string . IsNullOrEmpty ( email ) ? - 1 : 1 )
. Where ( user = > user ! = null )
. ToLookup ( lu = >
{
var ui = Constants . LostUser ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
try
{
if ( string . IsNullOrEmpty ( _ldapDomain ) )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
_ldapDomain = LdapUtils . DistinguishedNameToDomain ( lu . DistinguishedName ) ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
ui = _ldapObjectExtension . ToUserInfo ( lu , this , _log ) ;
}
catch ( Exception ex )
{
_log . ErrorFormat ( "FindLdapUser->ToUserInfo() failed. Error: {0}" , ex . ToString ( ) ) ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
return Tuple . Create ( ui , lu ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
} ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! users . Any ( ) )
return listResults ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
foreach ( var user in users )
{
var ui = user . Key . Item1 ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ui . Equals ( Constants . LostUser ) )
continue ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var ul = user . Key . Item2 ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var ldapLoginAttribute = ul . GetValue ( Settings . LoginAttribute ) as string ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( string . IsNullOrEmpty ( ldapLoginAttribute ) )
{
_log . WarnFormat ( "LDAP: DN: '{0}' Login Attribute '{1}' is empty" , ul . DistinguishedName , Settings . LoginAttribute ) ;
continue ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ldapLoginAttribute . Equals ( login ) )
{
listResults . Add ( user . Key ) ;
continue ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! string . IsNullOrEmpty ( email ) )
{
if ( ui . Email . Equals ( email , StringComparison . InvariantCultureIgnoreCase ) )
2022-03-08 05:37:20 +00:00
{
listResults . Add ( user . Key ) ;
2022-03-17 19:44:34 +00:00
continue ;
2022-03-08 05:37:20 +00:00
}
}
2022-03-17 19:44:34 +00:00
if ( LdapUtils . IsLoginAccepted ( ldapLogin , ui , LDAPDomain ) )
{
listResults . Add ( user . Key ) ;
}
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
return listResults ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
public List < LdapObject > FindUsersByAttribute ( string key , string value , StringComparison comparison = StringComparison . InvariantCultureIgnoreCase )
{
var users = new List < LdapObject > ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! AllDomainUsers . Any ( ) & & ! TryLoadLDAPUsers ( ) )
return users ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
return users . Where ( us = > ! us . IsDisabled & & string . Equals ( ( string ) us . GetValue ( key ) , value , comparison ) ) . ToList ( ) ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
public List < LdapObject > FindUsersByAttribute ( string key , IEnumerable < string > value , StringComparison comparison = StringComparison . InvariantCultureIgnoreCase )
{
var users = new List < LdapObject > ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! AllDomainUsers . Any ( ) & & ! TryLoadLDAPUsers ( ) )
return users ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
return AllDomainUsers . Where ( us = > ! us . IsDisabled & & value . Any ( val = > string . Equals ( val , ( string ) us . GetValue ( key ) , comparison ) ) ) . ToList ( ) ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
public List < LdapObject > FindGroupsByAttribute ( string key , string value , StringComparison comparison = StringComparison . InvariantCultureIgnoreCase )
{
var gr = new List < LdapObject > ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! AllDomainGroups . Any ( ) & & ! TryLoadLDAPGroups ( ) )
return gr ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
return gr . Where ( g = > ! g . IsDisabled & & string . Equals ( ( string ) g . GetValue ( key ) , value , comparison ) ) . ToList ( ) ;
}
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
public List < LdapObject > FindGroupsByAttribute ( string key , IEnumerable < string > value , StringComparison comparison = StringComparison . InvariantCultureIgnoreCase )
{
var gr = new List < LdapObject > ( ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
if ( ! AllDomainGroups . Any ( ) & & ! TryLoadLDAPGroups ( ) )
return gr ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
return AllDomainGroups . Where ( g = > ! g . IsDisabled & & value . Any ( val = > string . Equals ( val , ( string ) g . GetValue ( key ) , comparison ) ) ) . ToList ( ) ;
}
public Tuple < UserInfo , LdapObject > Login ( string login , string password )
{
try
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
if ( string . IsNullOrEmpty ( login ) | | string . IsNullOrEmpty ( password ) )
return null ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
var ldapUsers = FindLdapUsers ( login ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
_log . DebugFormat ( "FindLdapUsers(login '{0}') found: {1} users" , login , ldapUsers . Count ) ;
2022-03-08 05:37:20 +00:00
2022-03-17 19:44:34 +00:00
foreach ( var ldapUser in ldapUsers )
{
string currentLogin = null ;
try
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
var ldapUserInfo = ldapUser . Item1 ;
var ldapUserObject = ldapUser . Item2 ;
if ( ldapUserInfo . Equals ( Constants . LostUser )
| | ldapUserObject = = null )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
continue ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
else if ( string . IsNullOrEmpty ( ldapUserObject . DistinguishedName )
| | string . IsNullOrEmpty ( ldapUserObject . Sid ) )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
_log . DebugFormat ( "LdapUserImporter->Login(login: '{0}', dn: '{1}') failed. Error: missing DN or SID" , login , ldapUserObject . Sid ) ;
continue ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
currentLogin = ldapUserObject . DistinguishedName ;
_log . DebugFormat ( "LdapUserImporter.Login('{0}')" , currentLogin ) ;
LdapHelper . CheckCredentials ( currentLogin , password , Settings . Server ,
Settings . PortNumber , Settings . StartTls , Settings . Ssl , Settings . AcceptCertificate ,
Settings . AcceptCertificateHash ) ;
return new Tuple < UserInfo , LdapObject > ( ldapUserInfo , ldapUserObject ) ;
}
catch ( Exception ex )
{
_log . ErrorFormat ( "LdapUserImporter->Login(login: '{0}') failed. Error: {1}" , currentLogin ? ? login , ex ) ;
2022-03-08 05:37:20 +00:00
}
}
}
2022-03-17 19:44:34 +00:00
catch ( Exception ex )
2022-03-08 05:37:20 +00:00
{
2022-03-17 19:44:34 +00:00
_log . ErrorFormat ( "LdapUserImporter->Login({0}) failed {1}" , login , ex ) ;
2022-03-08 05:37:20 +00:00
}
2022-03-17 19:44:34 +00:00
return null ;
}
public void Dispose ( )
{
if ( LdapHelper ! = null )
LdapHelper . Dispose ( ) ;
2022-03-08 05:37:20 +00:00
}
}