Пример #1
0
        private async Task <int> UpdateReportAndDetails(List <ReportDetail> reports, ConnectorIdentifier identifier)
        {
            if (reports == null || reports.Count <= 0)
            {
                return(0);
            }

            // Update telemetry
            var count  = 0;
            var groups = reports.GroupBy(r => new { r.MessageId, r.CustomMessageId });

            foreach (var group in groups)
            {
                var succeed = group.Where(r => r.State == MessageState.DELIVERED).Count();
                var failed  = group.Where(r => r.State != MessageState.DELIVERED).Count();
                var updated = await this.telemetryManager.OnMessageReportUpdatedAsync(
                    group.Key.MessageId,
                    group.Key.CustomMessageId,
                    group.ToList(),
                    identifier);

                if (updated)
                {
                    count += group.Count();
                }
            }

            return(count);
        }
Пример #2
0
        public async Task DeleteCredentialAssignmentsAsync(string engagementAccount, ConnectorIdentifier identifier)
        {
            var assignments = await this.store.ListCredentialAssignmentsByAccountAsync(engagementAccount, ChannelType.Both, false);

            Validator.IsTrue <ResourceNotFoundException>(assignments != null || assignments.Any(a => a.ConnectorIdentifier.Equals(identifier)), nameof(identifier), "The assignment does not exist.");

            await this.store.DeleteCredentialAssignmentsAsync(engagementAccount, identifier);
        }
Пример #3
0
        public async Task <SmsConnectorCredential> GetConnectorCredentialByIdAsync(ConnectorIdentifier identifier)
        {
            var credential = await this.store.GetConnectorCredentialByIdAsync(identifier);

            Validator.IsTrue <ResourceNotFoundException>(credential != null, nameof(credential), "Credential '{0}' does not exist or is disabled.", identifier);

            return(credential);
        }
Пример #4
0
        public async Task <AgentMetadata> GetAgentMetadataAsync(ConnectorIdentifier identifier)
        {
            using (var ctx = new SmsServiceDbEntities(this.connectionString))
            {
                var entity = await ctx.ConnectorAgentMetadata.SingleOrDefaultAsync(
                    a => a.Provider == identifier.ConnectorName &&
                    a.Id == identifier.ConnectorId);

                return(entity?.ToModel());
            }
        }
Пример #5
0
        public async Task <EmailConnectorCredential> GetConnectorCredentialByIdAsync(ConnectorIdentifier identifier)
        {
            using (var ctx = new EmailServiceDbEntities(this.connectionString))
            {
                var entity = await ctx.ConnectorCredentials.SingleOrDefaultAsync(
                    c => c.Provider == identifier.ConnectorName &&
                    c.Id == identifier.ConnectorId);

                return(entity != null && entity.Enabled ? JsonConvert.DeserializeObject <EmailConnectorCredential>(entity.Data) : null);
            }
        }
Пример #6
0
        public async Task <ServiceProviderResponse> CreateOrUpdateCredentialAssignments(
            [FromHeader(Constant.OperationTrackingIdHeader)] string requestId,
            string account,
            [FromBody] CredentialAssignment request)
        {
            await ValidateAccount(account);

            Validator.ArgumentNotNull(request, nameof(request));
            Validator.ArgumentNotNullOrEmpty(request.Provider, nameof(request.Provider));
            Validator.ArgumentNotNullOrEmpty(request.ConnectorId, nameof(request.ConnectorId));
            Validator.IsTrue <ArgumentException>(request.ChannelType != ChannelType.Invalid, nameof(request.ChannelType), "Invalid channel type.");

            var identifier = new ConnectorIdentifier(request.Provider, request.ConnectorId);
            var credential = await this.credentialManager.GetConnectorCredentialByIdAsync(identifier);

            Validator.IsTrue <ResourceNotFoundException>(credential != null, nameof(credential), "Credential '{0}' does not exist.", identifier);
            Validator.IsTrue <ArgumentException>(credential.ChannelType == request.ChannelType, nameof(request.ChannelType), "Credential '{0}' is for channel type '{1}' but not '{2}'", identifier, credential.ChannelType.ToString(), request.ChannelType.ToString());

            if (string.IsNullOrEmpty(request.ExtendedCode))
            {
                // Auto-assign account code
                var otherAccounts = await this.credentialManager.ListCredentialAssignmentsById(identifier, false);

                if (otherAccounts != null && otherAccounts.Count > 0)
                {
                    var last = otherAccounts.Where(a => a.ExtendedCode != null).OrderBy(a => a.ExtendedCode).LastOrDefault();
                    if (int.TryParse(last.ExtendedCode, out int code) && code < Math.Pow(10, SmsConstant.ExtendedCodeCompanyLength) - 1)
                    {
                        request.ExtendedCode = (code + 1).ToString().PadLeft(SmsConstant.ExtendedCodeCompanyLength, '0');
                    }
                    else
                    {
                        throw new ApplicationException($"Failed the generate new code. The last one is '{last.ExtendedCode}'");
                    }
                }
                else
                {
                    request.ExtendedCode = 1.ToString().PadLeft(SmsConstant.ExtendedCodeCompanyLength, '0');
                }
            }

            await this.credentialManager.CreateOrUpdateCredentialAssignmentAsync(request.ToDataContract(account));

            var assignmentList = await this.credentialManager.ListCredentialAssignmentsByAccountAsync(account, ChannelType.Both, false);

            var response = assignmentList?.Select(a => new CredentialAssignment(a)).ToList();

            return(ServiceProviderResponse.CreateJsonResponse(HttpStatusCode.OK, response));
        }
Пример #7
0
        public async Task DeleteConnectorCredentialAsync(ConnectorIdentifier identifier)
        {
            using (var ctx = new EmailServiceDbEntities(this.connectionString))
            {
                var entity = await ctx.ConnectorCredentials.SingleOrDefaultAsync(
                    c => c.Provider == identifier.ConnectorName &&
                    c.Id == identifier.ConnectorId);

                if (entity != null)
                {
                    ctx.ConnectorCredentials.Remove(entity);
                    await ctx.SaveChangesAsync();
                }
            }
        }
Пример #8
0
        public async Task DeleteCredentialAssignmentsAsync(string engagementAccount, ConnectorIdentifier identifier)
        {
            using (var ctx = new EmailServiceDbEntities(this.connectionString))
            {
                var entities = await ctx.ConnectorCredentialAssignments.Where(c => c.EngagementAccount == engagementAccount).ToListAsync();

                entities = entities.Where(e => identifier == null || (e.Provider.Equals(identifier.ConnectorName, StringComparison.OrdinalIgnoreCase) && e.Id.Equals(identifier.ConnectorId, StringComparison.OrdinalIgnoreCase))).ToList();

                if (entities != null)
                {
                    ctx.ConnectorCredentialAssignments.RemoveRange(entities);
                    await ctx.SaveChangesAsync();
                }
            }
        }
Пример #9
0
 public async Task <List <ConnectorCredentialAssignment> > ListCredentialAssignmentsById(ConnectorIdentifier identifier, bool activeOnly = true)
 {
     return(await this.store.ListCredentialAssignmentsById(identifier, activeOnly));
 }
Пример #10
0
 private string GetAgentKey(ConnectorIdentifier identifer)
 {
     return(identifer != null?string.Concat(identifer.ConnectorName, identifer.ConnectorId) : null);
 }
Пример #11
0
 public async Task <int> OnReportPushedAsync(List <ReportDetail> reports, ConnectorIdentifier identifier)
 {
     return(await this.UpdateReportAndDetails(reports, identifier));
 }
Пример #12
0
        public async Task <HttpResponseMessage> OnInboundMessageReceived(string connectorName, HttpRequestMessage request, CancellationToken cancellationToken)
        {
            try
            {
                var metadata = await this.credentialManager.GetMetadata(connectorName);

                if (metadata != null)
                {
                    InboundAgent agent = null;
                    try
                    {
                        agent = new InboundAgent(metadata.ConnectorUri);

                        // Get credential
                        var connectorId = await agent.ParseConnectorIdFromInboundMessageAsync(request, cancellationToken);

                        if (string.IsNullOrEmpty(connectorId))
                        {
                            SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(OnInboundMessageReceived), OperationStates.FailedMatch, $"Failed to get connector key for '{connectorName}'.");
                            return(request.CreateResponse(HttpStatusCode.Unauthorized));
                        }

                        var identifier = new ConnectorIdentifier(connectorName, connectorId);
                        SmsConnectorCredential credential = null;

                        try
                        {
                            credential = await this.credentialManager.GetConnectorCredentialByIdAsync(identifier);
                        }
                        catch
                        {
                            SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(OnInboundMessageReceived), OperationStates.FailedMatch, $"Failed to get credential for {identifier}.");
                            return(request.CreateResponse(HttpStatusCode.Unauthorized));
                        }

                        // Parse and save messages
                        var result = await agent.ParseInboundRequestAsync(request, credential.ToDataContract(metadata), cancellationToken);

                        SmsProviderEventSource.Current.Info(SmsProviderEventSource.EmptyTrackingId, this, nameof(OnInboundMessageReceived), OperationStates.InProgress, $"Complete parsing code={result.Response.HttpStatusCode} message={result.Messages?.Count ?? 0}");

                        if (result.Type == InboundType.MoMessage)
                        {
                            await SaveMoMessageAsync(result.Messages, agent, cancellationToken);
                        }
                        else if (result.Type == InboundType.Report)
                        {
                            var saveResult = await SaveReportMessageAsync(result.Messages, identifier);

                            if (!saveResult)
                            {
                                result.Response.HttpStatusCode = (int)HttpStatusCode.InternalServerError;
                            }
                        }

                        SmsProviderEventSource.Current.Info(SmsProviderEventSource.EmptyTrackingId, this, nameof(OnInboundMessageReceived), OperationStates.Succeeded, $"Complete parsing code={result.Response.HttpStatusCode} message={result.Messages?.Count ?? 0}");
                        return(result.Response.ToHttpResponseMessage());
                    }
                    finally
                    {
                        if (agent != null)
                        {
                            agent.Dispose();
                        }
                    }
                }

                SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(OnInboundMessageReceived), OperationStates.FailedMatch, $"Inbound message received from invalid connector '{connectorName}'.");
                return(request.CreateResponse(HttpStatusCode.Forbidden));
            }
            catch (Exception ex)
            {
                SmsProviderEventSource.Current.ErrorException(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnInboundMessageReceived), OperationStates.Failed, "Failed to process inbound message", ex);
                return(request.CreateResponse(HttpStatusCode.InternalServerError));
            }
        }
Пример #13
0
        private async Task <bool> SaveReportMessageAsync(Dictionary <ConnectorIdentifier, List <InboundMessage> > messages, ConnectorIdentifier identifier)
        {
            if (messages == null || messages.Count <= 0)
            {
                return(false);
            }

            var reports = new List <ReportDetail>();

            foreach (var kv in messages)
            {
                reports.AddRange(kv.Value.Where(v => v.ReportMessage != null).Select(v => v.ReportMessage).ToList());
            }

            if (reports.Count > 0)
            {
                var saved = await this.reportManager.OnReportPushedAsync(reports, identifier);

                return(saved == reports.Count);
            }

            return(false);
        }
        public async Task <bool> OnMessageReportUpdatedAsync(string messageId, string customMessageId, List <ReportDetail> reports, ConnectorIdentifier connector)
        {
            if (string.IsNullOrEmpty(messageId) && !string.IsNullOrEmpty(customMessageId))
            {
                // Get id mapping, retry 3 times if cannot found (in case racing with case #2 who has not insert the mapping yet)
                int retry = 3;
                while (retry > 0)
                {
                    messageId = (await MessageIdMappingTableEntity.GetAsync(idMappingTable, customMessageId))?.MessageId;
                    if (!string.IsNullOrEmpty(messageId))
                    {
                        break;
                    }

                    retry--;
                    await Task.Delay(1000);
                }

                if (string.IsNullOrEmpty(messageId))
                {
                    SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Dropped, $"Does not find record for customMessageId={customMessageId}");
                    return(false);
                }
            }

            // Get record from summary table
            var entity = await MessageSummaryTableEntity.GetAsync(this.summaryTable, messageId);

            if (entity == null)
            {
                SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Dropped, $"Does not find record for messageId={messageId}");
                return(false);
            }

            // Calculating the unit according to provider
            // This is for long message that will be charged as multiple messages
            var metadata = await this.credentialManager.GetMetadata(connector.ConnectorName);

            var units = BillingHelper.CalculateBillingUnits(entity.MessageBody, metadata);

            // Report might be ealier than dispatch result, so make sure units is updated
            if (entity.Units == null)
            {
                entity.Units          = units;
                entity.LastUpdateTime = DateTime.UtcNow;
                await MessageSummaryTableEntity.InsertOrMergeAsync(this.summaryTable, entity);
            }

            // Update detail table
            if (reports != null && reports.Count > 0)
            {
                await this.UpdateMessageDetailsAsync(entity.EngagementAccount, entity, reports);
            }

            // Get account detail
            var account = await this.store.GetAccountAsync(entity.EngagementAccount);

            var succeed = reports.Where(r => r.State == MessageState.DELIVERED).Count();
            var failed  = reports.Where(r => r.State != MessageState.DELIVERED && r.State != MessageState.UNKNOWN).Count();

            // Push metric and billing usage
            if (Enum.TryParse(entity.MessageCategory, out MessageCategory category))
            {
                if (succeed > 0)
                {
                    this.metricManager.LogDeliverSuccess(succeed * units, entity.EngagementAccount, account?.SubscriptionId ?? string.Empty, category);

                    if (Constants.MessageCategoryToUsageTypeMappings.TryGetValue(category, out ResourceUsageType usageType))
                    {
                        var usage = new ResourceUsageRecord();
                        usage.EngagementAccount = entity.EngagementAccount;
                        usage.UsageType         = usageType;
                        usage.Quantity          = succeed * units;

                        await this.billingAgent.StoreBillingUsageAsync(new List <ResourceUsageRecord> {
                            usage
                        }, CancellationToken.None);

                        SmsProviderEventSource.Current.Info(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Succeeded, $"Usage pushed to billing service account={entity.EngagementAccount} usageType={usageType} quantity={succeed}x{units}");
                    }
                }

                if (failed > 0)
                {
                    this.metricManager.LogDeliverFailed(failed * units, entity.EngagementAccount, account?.SubscriptionId ?? string.Empty, category);
                }
            }
            else
            {
                SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Failed, $"Failed to parse MessageCategory={entity.MessageCategory} MessageId={entity.MessageId}");
            }

            return(true);
        }
Пример #15
0
        public async Task DeleteConnectorCredentialAsync(ConnectorIdentifier identifier)
        {
            var credential = GetConnectorCredentialByIdAsync(identifier);

            await this.store.DeleteConnectorCredentialAsync(identifier);
        }
        public async Task <bool> OnMessageDispatchedAsync(string engagementAccount, string messageId, string customMessageId, List <string> targets, ConnectorIdentifier connector)
        {
            if (!string.IsNullOrEmpty(customMessageId))
            {
                // Insert id mapping
                await MessageIdMappingTableEntity.InsertOrMergeAsync(this.idMappingTable, new MessageIdMappingTableEntity(engagementAccount, messageId, customMessageId));
            }

            // Get record from summary table
            var entity = await MessageSummaryTableEntity.GetAsync(this.summaryTable, messageId);

            if (entity == null)
            {
                SmsProviderEventSource.Current.Warning(SmsProviderEventSource.EmptyTrackingId, this, nameof(this.OnMessageReportUpdatedAsync), OperationStates.Dropped, $"Does not find record for account={engagementAccount} messageId={messageId}");
                return(false);
            }

            // Calculating the unit according to provider
            var metadata = await this.credentialManager.GetMetadata(connector.ConnectorName);

            var units = BillingHelper.CalculateBillingUnits(entity.MessageBody, metadata);

            if (entity.Units == null)
            {
                entity.Units          = units;
                entity.LastUpdateTime = DateTime.UtcNow;
                await MessageSummaryTableEntity.InsertOrMergeAsync(this.summaryTable, entity);
            }

            return(true);
        }
Пример #17
0
        public async Task <List <CredentialAssignment> > ListCredentialAssignmentsById(ConnectorIdentifier identifier, bool activeOnly = true)
        {
            using (var ctx = new EmailServiceDbEntities(this.connectionString))
            {
                var entities = await ctx.ConnectorCredentialAssignments.Where(
                    c => c.Provider == identifier.ConnectorName &&
                    c.Id == identifier.ConnectorId &&
                    (!activeOnly || c.Active)).ToListAsync();

                return(entities?.Select(e => e.ToModel()).ToList());
            }
        }