2019-06-14 11:56:32 +00:00
/ *
*
* ( c ) Copyright Ascensio System Limited 2010 - 2018
*
* This program is freeware . You can redistribute it and / or modify it under the terms of the GNU
* General Public License ( GPL ) version 3 as published by the Free Software Foundation ( https : //www.gnu.org/copyleft/gpl.html).
* In accordance with Section 7 ( a ) of the GNU GPL 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 more details , see GNU GPL at https : //www.gnu.org/copyleft/gpl.html
*
* You can contact Ascensio System SIA by email at sales @onlyoffice . com
*
* The interactive user interfaces in modified source and object code versions of ONLYOFFICE must display
* Appropriate Legal Notices , as required under Section 5 of the GNU GPL version 3.
*
* Pursuant to Section 7 § 3 ( b ) of the GNU GPL you must retain the original ONLYOFFICE logo which contains
* relevant author attributions when distributing the software . If the display of the logo in its graphic
* form is not reasonably feasible for technical reasons , you must include the words "Powered by ONLYOFFICE"
* in every copy of the program you distribute .
* Pursuant to Section 7 § 3 ( e ) we decline to grant you any rights under trademark law for use of our trademarks .
*
* /
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Threading ;
2019-10-18 14:22:40 +00:00
2019-06-14 11:56:32 +00:00
using ASC.Common.Data ;
using ASC.Common.Data.Sql ;
using ASC.Common.Data.Sql.Expressions ;
using ASC.Common.Logging ;
2019-12-05 08:51:28 +00:00
using ASC.Core.Common.EF ;
using ASC.Core.Common.EF.Context ;
using ASC.Core.Common.EF.Model ;
2019-06-14 11:56:32 +00:00
using ASC.Core.Tenants ;
2019-10-18 14:22:40 +00:00
using Microsoft.Extensions.DependencyInjection ;
2019-10-17 15:55:35 +00:00
using Microsoft.Extensions.Options ;
2019-10-18 14:22:40 +00:00
2019-06-14 11:56:32 +00:00
using Newtonsoft.Json ;
2019-10-18 14:22:40 +00:00
2019-06-14 11:56:32 +00:00
using UAParser ;
2019-10-18 14:22:40 +00:00
2019-06-14 11:56:32 +00:00
using IsolationLevel = System . Data . IsolationLevel ;
namespace ASC.MessagingSystem.DbSender
{
2019-10-10 08:52:21 +00:00
public class MessagesRepository
2019-06-14 11:56:32 +00:00
{
private static DateTime lastSave = DateTime . UtcNow ;
2019-10-10 08:52:21 +00:00
private readonly TimeSpan CacheTime ;
private readonly IDictionary < string , EventMessage > Cache ;
2019-06-14 11:56:32 +00:00
private static Parser Parser { get ; set ; }
2019-10-10 08:52:21 +00:00
public DbRegistry DbRegistry { get ; }
private readonly Timer Timer ;
private bool timerStarted ;
2019-06-14 11:56:32 +00:00
private const string LoginEventsTable = "login_events" ;
private const string AuditEventsTable = "audit_events" ;
2019-10-10 08:52:21 +00:00
private readonly Timer ClearTimer ;
2019-10-17 15:55:35 +00:00
public ILog Log { get ; set ; }
2019-10-18 14:22:40 +00:00
public IServiceProvider ServiceProvider { get ; }
2019-06-14 11:56:32 +00:00
2019-11-06 15:03:09 +00:00
public MessagesRepository ( IServiceProvider serviceProvider , IOptionsMonitor < ILog > options )
2019-06-14 11:56:32 +00:00
{
CacheTime = TimeSpan . FromMinutes ( 1 ) ;
Cache = new Dictionary < string , EventMessage > ( ) ;
Parser = Parser . GetDefault ( ) ;
Timer = new Timer ( FlushCache ) ;
timerStarted = false ;
ClearTimer = new Timer ( DeleteOldEvents ) ;
ClearTimer . Change ( new TimeSpan ( 0 ) , TimeSpan . FromDays ( 1 ) ) ;
2019-11-06 15:03:09 +00:00
Log = options . CurrentValue ;
2019-10-18 14:22:40 +00:00
ServiceProvider = serviceProvider ;
2019-06-14 11:56:32 +00:00
}
2019-10-10 08:52:21 +00:00
public void Add ( EventMessage message )
2019-06-14 11:56:32 +00:00
{
// messages with action code < 2000 are related to login-history
if ( ( int ) message . Action < 2000 )
{
2019-10-18 14:22:40 +00:00
using var scope = ServiceProvider . CreateScope ( ) ;
2019-12-05 08:51:28 +00:00
using var ef = scope . ServiceProvider . GetService < DbContextManager < MessagesContext > > ( ) . Get ( "messages" ) ;
AddLoginEvent ( message , ef ) ;
2019-06-14 11:56:32 +00:00
return ;
}
var now = DateTime . UtcNow ;
var key = string . Format ( "{0}|{1}|{2}|{3}" , message . TenantId , message . UserId , message . Id , now . Ticks ) ;
lock ( Cache )
{
Cache [ key ] = message ;
if ( ! timerStarted )
{
Timer . Change ( 0 , 100 ) ;
timerStarted = true ;
}
}
}
2019-10-10 08:52:21 +00:00
private void FlushCache ( object state )
2019-06-14 11:56:32 +00:00
{
List < EventMessage > events = null ;
if ( CacheTime < DateTime . UtcNow - lastSave | | Cache . Count > 100 )
{
lock ( Cache )
{
Timer . Change ( - 1 , - 1 ) ;
timerStarted = false ;
events = new List < EventMessage > ( Cache . Values ) ;
Cache . Clear ( ) ;
lastSave = DateTime . UtcNow ;
}
}
if ( events = = null ) return ;
2019-10-18 14:22:40 +00:00
using var scope = ServiceProvider . CreateScope ( ) ;
using var db = scope . ServiceProvider . GetService < DbOptionsManager > ( ) . Get ( "messages" ) ;
2019-12-05 08:51:28 +00:00
using var ef = scope . ServiceProvider . GetService < DbContextManager < MessagesContext > > ( ) . Get ( "messages" ) ;
2019-08-15 15:08:40 +00:00
using var tx = db . BeginTransaction ( IsolationLevel . ReadUncommitted ) ;
var dict = new Dictionary < string , ClientInfo > ( ) ;
2019-06-14 11:56:32 +00:00
2019-08-15 15:08:40 +00:00
foreach ( var message in events )
{
if ( ! string . IsNullOrEmpty ( message . UAHeader ) )
2019-06-14 11:56:32 +00:00
{
2019-08-15 15:08:40 +00:00
try
2019-06-14 11:56:32 +00:00
{
2019-08-15 15:08:40 +00:00
ClientInfo clientInfo ;
if ( dict . ContainsKey ( message . UAHeader ) )
{
clientInfo = dict [ message . UAHeader ] ;
2019-06-14 11:56:32 +00:00
}
2019-08-15 15:08:40 +00:00
else
2019-06-14 11:56:32 +00:00
{
2019-08-15 15:08:40 +00:00
clientInfo = Parser . Parse ( message . UAHeader ) ;
dict . Add ( message . UAHeader , clientInfo ) ;
2019-06-14 11:56:32 +00:00
}
2019-08-15 15:08:40 +00:00
if ( clientInfo ! = null )
{
message . Browser = GetBrowser ( clientInfo ) ;
message . Platform = GetPlatform ( clientInfo ) ;
}
}
catch ( Exception e )
2019-06-14 11:56:32 +00:00
{
2019-10-17 15:55:35 +00:00
Log . Error ( "FlushCache " + message . Id , e ) ;
2019-06-14 11:56:32 +00:00
}
}
2019-08-15 15:08:40 +00:00
// messages with action code < 2000 are related to login-history
if ( ( int ) message . Action > = 2000 )
{
2019-12-05 08:51:28 +00:00
AddAuditEvent ( message , ef ) ;
2019-08-15 15:08:40 +00:00
}
2019-06-14 11:56:32 +00:00
}
2019-08-15 15:08:40 +00:00
tx . Commit ( ) ;
2019-06-14 11:56:32 +00:00
}
2019-12-05 08:51:28 +00:00
private static void AddLoginEvent ( EventMessage message , MessagesContext dbContext )
2019-06-14 11:56:32 +00:00
{
2019-12-05 08:51:28 +00:00
var le = new LoginEvents
{
Ip = message . IP ,
Login = message . Initiator ,
Browser = message . Browser ,
Platform = message . Platform ,
Date = message . Date ,
TenantId = message . TenantId ,
UserId = message . UserId ,
Page = message . Page ,
Action = ( int ) message . Action
} ;
2019-06-14 11:56:32 +00:00
if ( message . Description ! = null & & message . Description . Any ( ) )
{
2019-12-05 08:51:28 +00:00
le . Description =
2019-06-14 11:56:32 +00:00
JsonConvert . SerializeObject ( message . Description , new JsonSerializerSettings
{
DateTimeZoneHandling = DateTimeZoneHandling . Utc
2019-12-05 08:51:28 +00:00
} ) ;
2019-06-14 11:56:32 +00:00
}
2019-12-05 08:51:28 +00:00
dbContext . LoginEvents . Add ( le ) ;
dbContext . SaveChanges ( ) ;
2019-06-14 11:56:32 +00:00
}
2019-12-05 08:51:28 +00:00
private static void AddAuditEvent ( EventMessage message , MessagesContext dbContext )
2019-06-14 11:56:32 +00:00
{
2019-12-05 08:51:28 +00:00
var ae = new AuditEvent
{
Ip = message . IP ,
Initiator = message . Initiator ,
Browser = message . Browser ,
Platform = message . Platform ,
Date = message . Date ,
TenantId = message . TenantId ,
UserId = message . UserId ,
Page = message . Page ,
Action = ( int ) message . Action ,
Target = message . Target ? . ToString ( )
} ;
2019-06-14 11:56:32 +00:00
if ( message . Description ! = null & & message . Description . Any ( ) )
{
2019-12-05 08:51:28 +00:00
ae . Description =
2019-06-14 11:56:32 +00:00
JsonConvert . SerializeObject ( GetSafeDescription ( message . Description ) , new JsonSerializerSettings
{
DateTimeZoneHandling = DateTimeZoneHandling . Utc
2019-12-05 08:51:28 +00:00
} ) ;
2019-06-14 11:56:32 +00:00
}
2019-12-05 08:51:28 +00:00
dbContext . AuditEvents . Add ( ae ) ;
dbContext . SaveChanges ( ) ;
2019-06-14 11:56:32 +00:00
}
private static IList < string > GetSafeDescription ( IEnumerable < string > description )
{
const int maxLength = 15000 ;
var currentLength = 0 ;
var safe = new List < string > ( ) ;
foreach ( var d in description . Where ( r = > r ! = null ) )
{
if ( currentLength + d . Length < = maxLength )
{
currentLength + = d . Length ;
safe . Add ( d ) ;
}
else
{
safe . Add ( d . Substring ( 0 , maxLength - currentLength - 3 ) + "..." ) ;
break ;
}
}
return safe ;
}
private static string GetBrowser ( ClientInfo clientInfo )
{
return clientInfo = = null
? null
: string . Format ( "{0} {1}" , clientInfo . UA . Family , clientInfo . UA . Major ) ;
}
private static string GetPlatform ( ClientInfo clientInfo )
{
return clientInfo = = null
? null
: string . Format ( "{0} {1}" , clientInfo . OS . Family , clientInfo . OS . Major ) ;
}
2019-10-10 08:52:21 +00:00
//TODO: move to external service
private void DeleteOldEvents ( object state )
2019-06-14 11:56:32 +00:00
{
try
{
GetOldEvents ( LoginEventsTable , "LoginHistoryLifeTime" ) ;
GetOldEvents ( AuditEventsTable , "AuditTrailLifeTime" ) ;
}
catch ( Exception ex )
{
2019-10-17 15:55:35 +00:00
Log . Error ( ex . Message , ex ) ;
2019-06-14 11:56:32 +00:00
}
}
2019-10-10 08:52:21 +00:00
private void GetOldEvents ( string table , string settings )
2019-06-14 11:56:32 +00:00
{
2019-09-13 11:18:27 +00:00
var sqlQueryLimit = string . Format ( "(IFNULL((SELECT JSON_EXTRACT(`Data`, '$.{0}') from webstudio_settings where tt.id = TenantID and id='{1}'), {2})) as tout" , settings , TenantAuditSettings . Guid , TenantAuditSettings . MaxLifeTime ) ;
2019-06-14 11:56:32 +00:00
var query = new SqlQuery ( table + " t1" )
. Select ( "t1.id" )
. Select ( sqlQueryLimit )
. Select ( "t1.`date` AS dout" )
. InnerJoin ( "tenants_tenants tt" , Exp . EqColumns ( "tt.id" , "t1.tenant_id" ) )
. Having ( Exp . Sql ( "dout < ADDDATE(UTC_DATE(), INTERVAL -tout DAY)" ) )
. SetMaxResults ( 1000 ) ;
List < int > ids ;
do
{
2019-10-18 14:22:40 +00:00
using var scope = ServiceProvider . CreateScope ( ) ;
var dbManager = scope . ServiceProvider . GetService < DbOptionsManager > ( ) . Get ( "messages" ) ;
2019-12-05 08:51:28 +00:00
using var ef = scope . ServiceProvider . GetService < DbContextManager < MessagesContext > > ( ) . Get ( "messages" ) ;
var ae = ef . AuditEvents
. Join ( ef . Tenants , r = > r . TenantId , r = > r . Id , ( audit , tenant ) = > audit )
. Select ( r = > new
{
r . Id ,
r . Date ,
date = ef . WebstudioSettings
. Where ( a = > a . TenantId = = r . TenantId & & a . Id = = TenantAuditSettings . Guid )
. Select ( r = > r . Data )
. FirstOrDefault ( )
? ? TenantAuditSettings . MaxLifeTime . ToString ( )
} )
. Where ( r = > r . Date < DateTime . UtcNow . AddDays ( - 1 ) )
. Take ( 1000 ) ;
ids = ae . ToList ( ) . Select ( r = > r . Id ) . ToList ( ) ;
2019-06-14 11:56:32 +00:00
2019-08-15 15:08:40 +00:00
if ( ! ids . Any ( ) ) return ;
2019-06-14 11:56:32 +00:00
2019-12-05 08:51:28 +00:00
//var deleteQuery = new SqlDelete(table).Where(Exp.In("id", ids));
2019-06-14 11:56:32 +00:00
2019-12-05 08:51:28 +00:00
//dbManager.ExecuteNonQuery(deleteQuery);
2019-06-14 11:56:32 +00:00
} while ( ids . Any ( ) ) ;
}
}
}