426 lines
18 KiB
C#
426 lines
18 KiB
C#
|
/*
|
||
|
*
|
||
|
* (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.Configuration;
|
||
|
using System.Diagnostics;
|
||
|
using System.IO;
|
||
|
using System.Linq;
|
||
|
using System.Runtime.Serialization.Formatters.Binary;
|
||
|
using System.Runtime.Serialization.Json;
|
||
|
using System.Text;
|
||
|
using ASC.Common.Data;
|
||
|
using ASC.Common.Data.Sql;
|
||
|
using ASC.Common.Data.Sql.Expressions;
|
||
|
using ASC.Common.Utils;
|
||
|
using ASC.Core.Common.Settings;
|
||
|
using ASC.Core.Tenants;
|
||
|
using ASC.Core.Users;
|
||
|
|
||
|
namespace ASC.Core.Data
|
||
|
{
|
||
|
public class DbTenantService : DbBaseService, ITenantService
|
||
|
{
|
||
|
private static TimeZoneInfo defaultTimeZone;
|
||
|
private List<string> forbiddenDomains;
|
||
|
|
||
|
|
||
|
public DbTenantService(ConnectionStringSettings connectionString)
|
||
|
: base(connectionString, null)
|
||
|
{
|
||
|
}
|
||
|
|
||
|
|
||
|
public void ValidateDomain(string domain)
|
||
|
{
|
||
|
using (var db = GetDb())
|
||
|
{
|
||
|
ValidateDomain(db, domain, Tenant.DEFAULT_TENANT, true);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public IEnumerable<Tenant> GetTenants(DateTime from, bool active = true)
|
||
|
{
|
||
|
return GetTenants(
|
||
|
Exp.And(
|
||
|
active
|
||
|
? Exp.Eq("t.status", (int)TenantStatus.Active)
|
||
|
: Exp.Empty,
|
||
|
from != default(DateTime)
|
||
|
? Exp.Ge("last_modified", from)
|
||
|
: Exp.Empty
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
public IEnumerable<Tenant> GetTenants(string login, string passwordHash)
|
||
|
{
|
||
|
if (string.IsNullOrEmpty(login)) throw new ArgumentNullException("login");
|
||
|
|
||
|
var q = TenantsQuery(Exp.Empty)
|
||
|
.InnerJoin("core_user u", Exp.EqColumns("t.id", "u.tenant"))
|
||
|
.InnerJoin("core_usersecurity s", Exp.EqColumns("u.id", "s.userid"))
|
||
|
.Where("t.status", (int)TenantStatus.Active)
|
||
|
.Where(login.Contains('@') ? "u.email" : "u.id", login)
|
||
|
.Where("u.status", EmployeeStatus.Active)
|
||
|
.Where("u.removed", false);
|
||
|
if (passwordHash != null)
|
||
|
{
|
||
|
q.Where("s.pwdhash", passwordHash);
|
||
|
}
|
||
|
return ExecList(q).ConvertAll(ToTenant);
|
||
|
}
|
||
|
|
||
|
public Tenant GetTenant(int id)
|
||
|
{
|
||
|
return GetTenants(Exp.Eq("id", id))
|
||
|
.SingleOrDefault();
|
||
|
}
|
||
|
|
||
|
public Tenant GetTenant(string domain)
|
||
|
{
|
||
|
if (string.IsNullOrEmpty(domain)) throw new ArgumentNullException("domain");
|
||
|
|
||
|
return GetTenants(Exp.Eq("alias", domain.ToLowerInvariant()) | Exp.Eq("mappeddomain", domain.ToLowerInvariant()))
|
||
|
.OrderBy(a => a.Status == TenantStatus.Restoring ? TenantStatus.Active : a.Status)
|
||
|
.ThenByDescending(a => a.Status == TenantStatus.Restoring ? 0 : a.TenantId)
|
||
|
.FirstOrDefault();
|
||
|
}
|
||
|
|
||
|
public Tenant GetTenantForStandaloneWithoutAlias(string ip)
|
||
|
{
|
||
|
return GetTenants(Exp.Empty)
|
||
|
.OrderBy(a => a.Status)
|
||
|
.ThenByDescending(a => a.TenantId)
|
||
|
.FirstOrDefault();
|
||
|
}
|
||
|
|
||
|
public Tenant SaveTenant(Tenant t)
|
||
|
{
|
||
|
if (t == null) throw new ArgumentNullException("tenant");
|
||
|
|
||
|
using (var db = GetDb())
|
||
|
using (var tx = db.BeginTransaction())
|
||
|
{
|
||
|
if (!string.IsNullOrEmpty(t.MappedDomain))
|
||
|
{
|
||
|
var baseUrl = TenantUtil.GetBaseDomain(t.HostedRegion);
|
||
|
|
||
|
if (baseUrl != null && t.MappedDomain.EndsWith("." + baseUrl, StringComparison.InvariantCultureIgnoreCase))
|
||
|
{
|
||
|
ValidateDomain(db, t.MappedDomain.Substring(0, t.MappedDomain.Length - baseUrl.Length - 1), t.TenantId, false);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
ValidateDomain(db, t.MappedDomain, t.TenantId, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (t.TenantId == Tenant.DEFAULT_TENANT)
|
||
|
{
|
||
|
var q = new SqlQuery("tenants_version")
|
||
|
.Select("id")
|
||
|
.Where(Exp.Eq("default_version", 1) | Exp.Eq("id", 0))
|
||
|
.OrderBy(1, false)
|
||
|
.SetMaxResults(1);
|
||
|
t.Version = db.ExecuteScalar<int>(q);
|
||
|
|
||
|
var i = new SqlInsert("tenants_tenants", true)
|
||
|
.InColumnValue("id", t.TenantId)
|
||
|
.InColumnValue("alias", t.TenantAlias.ToLowerInvariant())
|
||
|
.InColumnValue("mappeddomain", !string.IsNullOrEmpty(t.MappedDomain) ? t.MappedDomain.ToLowerInvariant() : null)
|
||
|
.InColumnValue("version", t.Version)
|
||
|
.InColumnValue("version_changed", t.VersionChanged)
|
||
|
.InColumnValue("name", t.Name ?? t.TenantAlias)
|
||
|
.InColumnValue("language", t.Language)
|
||
|
.InColumnValue("timezone", t.TimeZone.Id)
|
||
|
.InColumnValue("owner_id", t.OwnerId.ToString())
|
||
|
.InColumnValue("trusteddomains", t.GetTrustedDomains())
|
||
|
.InColumnValue("trusteddomainsenabled", (int)t.TrustedDomainsType)
|
||
|
.InColumnValue("creationdatetime", t.CreatedDateTime)
|
||
|
.InColumnValue("status", (int)t.Status)
|
||
|
.InColumnValue("statuschanged", t.StatusChangeDate)
|
||
|
.InColumnValue("payment_id", t.PaymentId)
|
||
|
.InColumnValue("last_modified", t.LastModified = DateTime.UtcNow)
|
||
|
.InColumnValue("industry", (int)t.Industry)
|
||
|
.InColumnValue("spam", t.Spam)
|
||
|
.InColumnValue("calls", t.Calls)
|
||
|
.Identity<int>(0, t.TenantId, true);
|
||
|
|
||
|
|
||
|
t.TenantId = db.ExecuteScalar<int>(i);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var u = new SqlUpdate("tenants_tenants")
|
||
|
.Set("alias", t.TenantAlias.ToLowerInvariant())
|
||
|
.Set("mappeddomain", !string.IsNullOrEmpty(t.MappedDomain) ? t.MappedDomain.ToLowerInvariant() : null)
|
||
|
.Set("version", t.Version)
|
||
|
.Set("version_changed", t.VersionChanged)
|
||
|
.Set("name", t.Name ?? t.TenantAlias)
|
||
|
.Set("language", t.Language)
|
||
|
.Set("timezone", t.TimeZone.Id)
|
||
|
.Set("owner_id", t.OwnerId.ToString())
|
||
|
.Set("trusteddomains", t.GetTrustedDomains())
|
||
|
.Set("trusteddomainsenabled", (int)t.TrustedDomainsType)
|
||
|
.Set("creationdatetime", t.CreatedDateTime)
|
||
|
.Set("status", (int)t.Status)
|
||
|
.Set("statuschanged", t.StatusChangeDate)
|
||
|
.Set("payment_id", t.PaymentId)
|
||
|
.Set("last_modified", t.LastModified = DateTime.UtcNow)
|
||
|
.Set("industry", (int)t.Industry)
|
||
|
.Set("spam", t.Spam)
|
||
|
.Set("calls", t.Calls)
|
||
|
.Where("id", t.TenantId);
|
||
|
|
||
|
db.ExecuteNonQuery(u);
|
||
|
}
|
||
|
|
||
|
if (string.IsNullOrEmpty(t.PartnerId) && string.IsNullOrEmpty(t.AffiliateId))
|
||
|
{
|
||
|
var d = new SqlDelete("tenants_partners").Where("tenant_id", t.TenantId);
|
||
|
db.ExecuteNonQuery(d);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var i = new SqlInsert("tenants_partners", true)
|
||
|
.InColumnValue("tenant_id", t.TenantId)
|
||
|
.InColumnValue("partner_id", t.PartnerId)
|
||
|
.InColumnValue("affiliate_id", t.AffiliateId);
|
||
|
db.ExecuteNonQuery(i);
|
||
|
}
|
||
|
|
||
|
tx.Commit();
|
||
|
}
|
||
|
//CalculateTenantDomain(t);
|
||
|
return t;
|
||
|
}
|
||
|
|
||
|
public void RemoveTenant(int id, bool auto = false)
|
||
|
{
|
||
|
var postfix = auto ? "_auto_deleted" : "_deleted";
|
||
|
|
||
|
using (var db = GetDb())
|
||
|
using (var tx = db.BeginTransaction())
|
||
|
{
|
||
|
var alias = db.ExecuteScalar<string>(new SqlQuery("tenants_tenants").Select("alias").Where("id", id));
|
||
|
var count = db.ExecuteScalar<int>(new SqlQuery("tenants_tenants").SelectCount().Where(Exp.Like("alias", alias + postfix, SqlLike.StartWith)));
|
||
|
db.ExecuteNonQuery(
|
||
|
new SqlUpdate("tenants_tenants")
|
||
|
.Set("alias", alias + postfix + (count > 0 ? count.ToString() : ""))
|
||
|
.Set("status", TenantStatus.RemovePending)
|
||
|
.Set("statuschanged", DateTime.UtcNow)
|
||
|
.Set("last_modified", DateTime.UtcNow)
|
||
|
.Where("id", id));
|
||
|
|
||
|
tx.Commit();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public IEnumerable<TenantVersion> GetTenantVersions()
|
||
|
{
|
||
|
var q = new SqlQuery("tenants_version")
|
||
|
.Select("id", "version")
|
||
|
.Where("visible", true);
|
||
|
return ExecList(q)
|
||
|
.ConvertAll(r => new TenantVersion(Convert.ToInt32(r[0]), (string)r[1]));
|
||
|
}
|
||
|
|
||
|
|
||
|
public byte[] GetTenantSettings(int tenant, string key)
|
||
|
{
|
||
|
return ExecScalar<byte[]>(new SqlQuery("core_settings").Select("value").Where("tenant", tenant).Where("id", key));
|
||
|
}
|
||
|
|
||
|
public void SetTenantSettings(int tenant, string key, byte[] data)
|
||
|
{
|
||
|
var i = data == null || data.Length == 0 ?
|
||
|
(ISqlInstruction)new SqlDelete("core_settings").Where("tenant", tenant).Where("id", key) :
|
||
|
(ISqlInstruction)new SqlInsert("core_settings", true).InColumns("tenant", "id", "value").Values(tenant, key, data);
|
||
|
ExecNonQuery(i);
|
||
|
}
|
||
|
|
||
|
private IEnumerable<Tenant> GetTenants(Exp where)
|
||
|
{
|
||
|
var q = TenantsQuery(where);
|
||
|
return ExecList(q).ConvertAll(ToTenant);
|
||
|
}
|
||
|
|
||
|
private SqlQuery TenantsQuery(Exp where)
|
||
|
{
|
||
|
return new SqlQuery("tenants_tenants t")
|
||
|
.Select("t.id", "t.alias", "t.mappeddomain", "t.version", "t.version_changed", "t.name", "t.language", "t.timezone", "t.owner_id")
|
||
|
.Select("t.trusteddomains", "t.trusteddomainsenabled", "t.creationdatetime", "t.status", "t.statuschanged", "t.payment_id", "t.last_modified")
|
||
|
.Select("p.partner_id", "p.affiliate_id")
|
||
|
.Select("t.industry", "t.spam", "t.calls")
|
||
|
.LeftOuterJoin("tenants_partners p", Exp.EqColumns("t.id", "p.tenant_id"))
|
||
|
.Where(where);
|
||
|
}
|
||
|
|
||
|
private Tenant ToTenant(object[] r)
|
||
|
{
|
||
|
var tenant = new Tenant(Convert.ToInt32(r[0]), (string)r[1])
|
||
|
{
|
||
|
MappedDomain = (string)r[2],
|
||
|
Version = Convert.ToInt32(r[3]),
|
||
|
VersionChanged = Convert.ToDateTime(r[4]),
|
||
|
Name = (string)r[5],
|
||
|
Language = (string)r[6],
|
||
|
TimeZone = GetTimeZone((string)r[7]),
|
||
|
OwnerId = r[8] != null ? new Guid((string)r[8]) : Guid.Empty,
|
||
|
TrustedDomainsType = (TenantTrustedDomainsType)Convert.ToInt32(r[10]),
|
||
|
CreatedDateTime = (DateTime)r[11],
|
||
|
Status = (TenantStatus)Convert.ToInt32(r[12]),
|
||
|
StatusChangeDate = r[13] != null ? (DateTime)r[13] : default(DateTime),
|
||
|
PaymentId = (string)r[14],
|
||
|
LastModified = (DateTime)r[15],
|
||
|
PartnerId = (string)r[16],
|
||
|
AffiliateId = (string)r[17],
|
||
|
Industry = r[18] != null ? (TenantIndustry)Convert.ToInt32(r[18]) : TenantIndustry.Other,
|
||
|
Spam = Convert.ToBoolean(r[19]),
|
||
|
Calls = Convert.ToBoolean(r[20])
|
||
|
};
|
||
|
tenant.SetTrustedDomains((string)r[9]);
|
||
|
|
||
|
return tenant;
|
||
|
}
|
||
|
|
||
|
private TimeZoneInfo GetTimeZone(string zoneId)
|
||
|
{
|
||
|
if (defaultTimeZone == null)
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
var tz = TimeZoneInfo.Local;
|
||
|
if (Path.DirectorySeparatorChar == '/')
|
||
|
{
|
||
|
if (tz.StandardName == "UTC" || tz.StandardName == "UCT")
|
||
|
{
|
||
|
tz = TimeZoneInfo.Utc;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var id = string.Empty;
|
||
|
if (File.Exists("/etc/timezone"))
|
||
|
{
|
||
|
id = File.ReadAllText("/etc/timezone").Trim();
|
||
|
}
|
||
|
|
||
|
if(string.IsNullOrEmpty(id))
|
||
|
{
|
||
|
var psi = new ProcessStartInfo
|
||
|
{
|
||
|
FileName = "/bin/bash",
|
||
|
Arguments = "date +%Z",
|
||
|
RedirectStandardOutput = true,
|
||
|
UseShellExecute = false,
|
||
|
};
|
||
|
using (var p = Process.Start(psi))
|
||
|
{
|
||
|
if (p.WaitForExit(1000))
|
||
|
{
|
||
|
id = p.StandardOutput.ReadToEnd();
|
||
|
}
|
||
|
p.Close();
|
||
|
}
|
||
|
}
|
||
|
if (!string.IsNullOrEmpty(id))
|
||
|
{
|
||
|
tz = TimeZoneInfo.GetSystemTimeZones().FirstOrDefault(z => z.Id == id) ?? tz;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
defaultTimeZone = tz;
|
||
|
}
|
||
|
catch(Exception)
|
||
|
{
|
||
|
// ignore
|
||
|
defaultTimeZone = TimeZoneInfo.Utc;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return string.IsNullOrEmpty(zoneId) ? defaultTimeZone : TimeZoneConverter.GetTimeZone(zoneId);
|
||
|
}
|
||
|
|
||
|
private void ValidateDomain(IDbManager db, string domain, int tenantId, bool validateCharacters)
|
||
|
{
|
||
|
// size
|
||
|
TenantDomainValidator.ValidateDomainLength(domain);
|
||
|
|
||
|
// characters
|
||
|
if (validateCharacters)
|
||
|
{
|
||
|
TenantDomainValidator.ValidateDomainCharacters(domain);
|
||
|
}
|
||
|
|
||
|
// forbidden or exists
|
||
|
var exists = false;
|
||
|
domain = domain.ToLowerInvariant();
|
||
|
if (!exists)
|
||
|
{
|
||
|
if (forbiddenDomains == null)
|
||
|
{
|
||
|
forbiddenDomains = ExecList(new SqlQuery("tenants_forbiden").Select("address")).Select(r => (string)r[0]).ToList();
|
||
|
}
|
||
|
exists = tenantId != 0 && forbiddenDomains.Contains(domain);
|
||
|
}
|
||
|
if (!exists)
|
||
|
{
|
||
|
exists = 0 < db.ExecuteScalar<int>(new SqlQuery("tenants_tenants").SelectCount().Where(Exp.Eq("alias", domain) & !Exp.Eq("id", tenantId)));
|
||
|
}
|
||
|
if (!exists)
|
||
|
{
|
||
|
exists = 0 < db.ExecuteScalar<int>(new SqlQuery("tenants_tenants").SelectCount()
|
||
|
.Where(Exp.Eq("mappeddomain", domain) & !Exp.Eq("id", tenantId) & !Exp.In("status", new []{(int)TenantStatus.RemovePending, (int) TenantStatus.Restoring})));
|
||
|
}
|
||
|
if (exists)
|
||
|
{
|
||
|
// cut number suffix
|
||
|
while (true)
|
||
|
{
|
||
|
if (6 < domain.Length && char.IsNumber(domain, domain.Length - 1))
|
||
|
{
|
||
|
domain = domain.Substring(0, domain.Length - 1);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var q = new SqlQuery("tenants_forbiden").Select("address").Where(Exp.Like("address", domain, SqlLike.StartWith))
|
||
|
.Union(new SqlQuery("tenants_tenants").Select("alias").Where(Exp.Like("alias", domain, SqlLike.StartWith) & !Exp.Eq("id", tenantId)))
|
||
|
.Union(new SqlQuery("tenants_tenants").Select("mappeddomain")
|
||
|
.Where(Exp.Like("mappeddomain", domain, SqlLike.StartWith) & !Exp.Eq("id", tenantId) & !Exp.Eq("status", (int)TenantStatus.RemovePending)));
|
||
|
|
||
|
var existsTenants = db.ExecuteList(q).ConvertAll(r => (string)r[0]);
|
||
|
throw new TenantAlreadyExistsException("Address busy.", existsTenants);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|