diff --git a/FoxIDs.sln b/FoxIDs.sln
index 970af340d..32ed5c578 100644
--- a/FoxIDs.sln
+++ b/FoxIDs.sln
@@ -270,6 +270,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docker", "Docker", "{93A3AF
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Azure", "Azure", "{376A9D7A-D64B-4526-8EFC-D21A3B77612B}"
ProjectSection(SolutionItems) = preProject
+ azuredeploy-small.json = azuredeploy-small.json
azuredeploy.json = azuredeploy.json
EndProjectSection
EndProject
diff --git a/Kubernetes/k8s-foxids-ingress-deployment.yaml b/Kubernetes/k8s-foxids-ingress-deployment.yaml
index 77f0ddb7b..18d65b078 100644
--- a/Kubernetes/k8s-foxids-ingress-deployment.yaml
+++ b/Kubernetes/k8s-foxids-ingress-deployment.yaml
@@ -26,7 +26,7 @@ spec:
name: foxids
port:
number: 8800
- - host: control.itfoxtec.com
+ - host: control.itfoxtec.com # change to your domain - control.my-domain.com
http:
paths:
- path: /
diff --git a/azuredeploy-small.json b/azuredeploy-small.json
new file mode 100644
index 000000000..9be26d688
--- /dev/null
+++ b/azuredeploy-small.json
@@ -0,0 +1,625 @@
+{
+ "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#",
+ "contentVersion": "1.0.0.0",
+ "parameters": {
+ "appServicePlanSize": {
+ "defaultValue": "P0V3",
+ "allowedValues": [
+ "F1",
+ "B1",
+ "B2",
+ "B3",
+ "S1",
+ "S2",
+ "S3",
+ "P1V2",
+ "P2V2",
+ "P3V2",
+ "P0V3",
+ "P1V3",
+ "P2V3",
+ "P3V3"
+ ],
+ "type": "string",
+ "metadata": {
+ "description": "The instance size of the App Service Plan."
+ }
+ },
+ "appServicePlanSku": {
+ "defaultValue": "Standard",
+ "allowedValues": [
+ "Free",
+ "Shared",
+ "Basic",
+ "Standard",
+ "Premium"
+ ],
+ "type": "string",
+ "metadata": {
+ "description": "The pricing tier of the App Service plan."
+ }
+ },
+ "keyVaultSkuName": {
+ "type": "string",
+ "defaultValue": "Standard",
+ "allowedValues": [
+ "Standard",
+ "Premium"
+ ],
+ "metadata": {
+ "description": "Specifies whether the key vault is a standard vault or a premium vault."
+ }
+ },
+ "sendgridFromEmail": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional, Sendgrid from email address."
+ }
+ },
+ "sendgridApiKey": {
+ "type": "string",
+ "defaultValue": "",
+ "metadata": {
+ "description": "Optional, Sendgrid API key."
+ }
+ }
+ },
+ "variables": {
+ "prefixName": "foxids",
+ "suffix": "[uniqueString(resourceGroup().id, resourceGroup().location)]",
+ "foxidsDefaultName": "[toLower(concat(variables('prefixName'), variables('suffix')))]",
+ "foxidsControlSiteName": "[toLower(concat(variables('prefixName'), 'control', variables('suffix')))]",
+ "foxidsSiteEndpoint": "[concat('https://', variables('foxidsDefaultName'), '.azurewebsites.net')]",
+ "foxidsControlSiteEndpoint": "[concat('https://', variables('foxidsControlSiteName'), '.azurewebsites.net')]"
+ },
+ "resources": [
+ {
+ "type": "Microsoft.DocumentDB/databaseAccounts",
+ "apiVersion": "2022-08-15",
+ "name": "[variables('foxidsDefaultName')]",
+ "location": "[resourceGroup().location]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('foxidsDefaultName'), 'subnet-data')]"
+ ],
+ "tags": {
+ "defaultExperience": "DocumentDB"
+ },
+ "kind": "GlobalDocumentDB",
+ "properties": {
+ "enableAutomaticFailover": false,
+ "enableMultipleWriteLocations": false,
+ "isVirtualNetworkFilterEnabled": true,
+ "virtualNetworkRules": [
+ {
+ "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('foxidsDefaultName'), 'subnet-data')]",
+ "ignoreMissingVNetServiceEndpoint": false
+ }
+ ],
+ "databaseAccountOfferType": "Standard",
+ "consistencyPolicy": {
+ "defaultConsistencyLevel": "Session",
+ "maxIntervalInSeconds": 5,
+ "maxStalenessPrefix": 100
+ },
+ "locations": [
+ {
+ "locationName": "[resourceGroup().location]",
+ "provisioningState": "Succeeded",
+ "failoverPriority": 0
+ }
+ ],
+ "capabilities": []
+ }
+ },
+ {
+ "type": "microsoft.operationalinsights/workspaces",
+ "apiVersion": "2021-12-01-preview",
+ "name": "[variables('foxidsDefaultName')]",
+ "location": "[resourceGroup().location]",
+ "properties": {
+ "sku": {
+ "name": "pergb2018"
+ },
+ "retentionInDays": 30,
+ "features": {
+ "enableLogAccessUsingOnlyResourcePermissions": true
+ },
+ "workspaceCapping": {
+ "dailyQuotaGb": -1
+ }
+ }
+ },
+ {
+ "type": "microsoft.insights/components",
+ "apiVersion": "2020-02-02",
+ "name": "[variables('foxidsDefaultName')]",
+ "location": "[resourceGroup().location]",
+ "dependsOn": [
+ "[resourceId('microsoft.operationalinsights/workspaces', variables('foxidsDefaultName'))]"
+ ],
+ "tags": {
+ "[concat('hidden-link:', resourceGroup().id, '/providers/Microsoft.Web/sites/', variables('foxidsDefaultName'))]": "Resource",
+ "[concat('hidden-link:', resourceGroup().id, '/providers/Microsoft.Web/sites/', variables('foxidsControlSiteName'))]": "Resource"
+ },
+ "kind": "web",
+ "properties": {
+ "Application_Type": "web",
+ "Flow_Type": "Redfield",
+ "Request_Source": "IbizaAIExtension",
+ "DisableIpMasking": true,
+ "RetentionInDays": 90,
+ "WorkspaceResourceId": "[resourceId('microsoft.operationalinsights/workspaces', variables('foxidsDefaultName'))]",
+ "IngestionMode": "LogAnalytics"
+ }
+ },
+ {
+ "type": "Microsoft.KeyVault/vaults",
+ "apiVersion": "2022-07-01",
+ "name": "[variables('foxidsDefaultName')]",
+ "location": "[resourceGroup().location]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('foxidsDefaultName'), 'subnet-data')]",
+ "[resourceId('Microsoft.Web/sites', variables('foxidsDefaultName'))]",
+ "[resourceId('Microsoft.Web/sites', variables('foxidsControlSiteName'))]"
+ ],
+ "properties": {
+ "sku": {
+ "family": "A",
+ "name": "[parameters('keyVaultSkuName')]"
+ },
+ "tenantId": "[subscription().tenantId]",
+ "networkAcls": {
+ "bypass": "None",
+ "defaultAction": "Deny",
+ "ipRules": [],
+ "virtualNetworkRules": [
+ {
+ "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('foxidsDefaultName'), 'subnet-data')]",
+ "ignoreMissingVnetServiceEndpoint": false
+ }
+ ]
+ },
+ "accessPolicies": [
+ {
+ "tenantId": "[subscription().tenantId]",
+ "objectId": "[reference(concat('Microsoft.Web/sites/', variables('foxidsDefaultName')), '2018-02-01', 'Full').identity.principalId]",
+ "permissions": {
+ "keys": [
+ "Get",
+ "List",
+ "Decrypt",
+ "Sign"
+ ],
+ "secrets": [
+ "get",
+ "List",
+ "Set"
+ ],
+ "certificates": [
+ "Get",
+ "List",
+ "Create"
+ ]
+ }
+ },
+ {
+ "tenantId": "[subscription().tenantId]",
+ "objectId": "[reference(concat('Microsoft.Web/sites/', variables('foxidsControlSiteName')), '2018-02-01', 'Full').identity.principalId]",
+ "permissions": {
+ "keys": [
+ "Get",
+ "List"
+ ],
+ "secrets": [
+ "get",
+ "List",
+ "Set",
+ "Delete"
+ ],
+ "certificates": [
+ "Get",
+ "List",
+ "Create",
+ "Delete",
+ "Import",
+ "Update"
+ ]
+ }
+ }
+ ],
+ "enabledForDeployment": false,
+ "enabledForDiskEncryption": false,
+ "enabledForTemplateDeployment": false,
+ "enableSoftDelete": true
+ }
+ },
+ {
+ "type": "Microsoft.Web/serverfarms",
+ "apiVersion": "2018-02-01",
+ "name": "[variables('foxidsDefaultName')]",
+ "location": "[resourceGroup().location]",
+ "sku": {
+ "name": "[parameters('appServicePlanSize')]",
+ "tier": "[parameters('appServicePlanSku')]",
+ "capacity": 1
+ },
+ "properties": {
+ "name": "[variables('foxidsDefaultName')]",
+ "workerSize": "0",
+ "numberOfWorkers": "1",
+ "reserved": true
+ }
+ },
+ {
+ "type": "Microsoft.Web/sites",
+ "apiVersion": "2023-01-01",
+ "name": "[variables('foxidsDefaultName')]",
+ "location": "[resourceGroup().location]",
+ "kind": "app,linux,container",
+ "dependsOn": [
+ "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('foxidsDefaultName'), 'subnet-data')]",
+ "[concat('Microsoft.Web/serverfarms/', variables('foxidsDefaultName'))]"
+ ],
+ "identity": {
+ "type": "SystemAssigned"
+ },
+ "properties": {
+ "name": "[variables('foxidsDefaultName')]",
+ "siteConfig": {
+ "linuxFxVersion": "DOCKER|foxids/foxids:latest",
+ "ftpsState": "Disabled",
+ "alwaysOn": true
+ },
+ "reserved": true,
+ "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('foxidsDefaultName'))]",
+ "clientAffinityEnabled": false,
+ "virtualNetworkSubnetId": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('foxidsDefaultName'), 'subnet-data')]"
+ }
+ },
+ {
+ "type": "Microsoft.Web/sites",
+ "apiVersion": "2023-01-01",
+ "name": "[variables('foxidsControlSiteName')]",
+ "location": "[resourceGroup().location]",
+ "kind": "app,linux,container",
+ "dependsOn": [
+ "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('foxidsDefaultName'), 'subnet-data')]",
+ "[concat('Microsoft.Web/serverfarms/', variables('foxidsDefaultName'))]"
+ ],
+ "identity": {
+ "type": "SystemAssigned"
+ },
+ "properties": {
+ "name": "[variables('foxidsControlSiteName')]",
+ "siteConfig": {
+ "linuxFxVersion": "DOCKER|foxids/foxids-control:latest",
+ "ftpsState": "Disabled",
+ "alwaysOn": true
+ },
+ "reserved": true,
+ "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('foxidsDefaultName'))]",
+ "clientAffinityEnabled": false,
+ "virtualNetworkSubnetId": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('foxidsDefaultName'), 'subnet-data')]"
+ }
+ },
+ {
+ "type": "Microsoft.Web/sites/config",
+ "apiVersion": "2023-01-01",
+ "name": "[concat(variables('foxidsDefaultName'), '/appsettings')]",
+ "dependsOn": [
+ "[concat('Microsoft.Web/sites/', variables('foxidsDefaultName'))]",
+ "[resourceId('microsoft.insights/components', variables('foxidsDefaultName'))]",
+ "[concat('Microsoft.DocumentDB/databaseAccounts/', variables('foxidsDefaultName'))]",
+ "[concat('Microsoft.KeyVault/vaults/', variables('foxidsDefaultName'))]"
+ ],
+ "properties": {
+ "WEBSITES_ENABLE_APP_SERVICE_STORAGE": false,
+ "Settings__FoxIDsEndpoint": "[variables('foxidsSiteEndpoint')]",
+ "DOCKER_REGISTRY_SERVER_URL": "https://index.docker.io/v1",
+ "DOCKER_ENABLE_CI": true,
+ "ASPNETCORE_URLS": "http://+",
+ "Settings__UseHttp": true,
+ "Settings__TrustProxySchemeHeader": true,
+ "Settings__Options__Log": "ApplicationInsights",
+ "Settings__Options__DataStorage": "CosmosDb",
+ "Settings__Options__KeyStorage": "KeyVault",
+ "Settings__Options__Cache": "Memory",
+ "Settings__Options__DataCache": "None",
+ "ApplicationInsights__ConnectionString": "[reference(concat('microsoft.insights/components/', variables('foxidsDefaultName'))).ConnectionString]",
+ "Settings__CosmosDb__EndpointUri": "[reference(concat('Microsoft.DocumentDb/databaseAccounts/', variables('foxidsDefaultName'))).documentEndpoint]",
+ "Settings__KeyVault__EndpointUri": "[reference(concat('Microsoft.KeyVault/vaults/', variables('foxidsDefaultName'))).vaultUri]",
+ "Settings__Sendgrid__FromEmail": "[parameters('sendgridFromEmail')]"
+ }
+ },
+ {
+ "type": "Microsoft.Web/sites/config",
+ "apiVersion": "2023-01-01",
+ "name": "[concat(variables('foxidsControlSiteName'), '/appsettings')]",
+ "dependsOn": [
+ "[concat('Microsoft.Web/sites/', variables('foxidsControlSiteName'))]",
+ "[resourceId('microsoft.insights/components', variables('foxidsDefaultName'))]",
+ "[concat('Microsoft.DocumentDB/databaseAccounts/', variables('foxidsDefaultName'))]",
+ "[concat('Microsoft.KeyVault/vaults/', variables('foxidsDefaultName'))]"
+ ],
+ "properties": {
+ "WEBSITES_ENABLE_APP_SERVICE_STORAGE": false,
+ "ApplicationInsights__ConnectionString": "[reference(concat('microsoft.insights/components/', variables('foxidsDefaultName'))).ConnectionString]",
+ "DOCKER_REGISTRY_SERVER_URL": "https://index.docker.io/v1",
+ "DOCKER_ENABLE_CI": true,
+ "ASPNETCORE_URLS": "http://+",
+ "Settings__UseHttp": true,
+ "Settings__TrustProxySchemeHeader": true,
+ "Settings__FoxIDsEndpoint": "[variables('foxidsSiteEndpoint')]",
+ "Settings__FoxIDsControlEndpoint": "[variables('foxidsControlSiteEndpoint')]",
+ "Settings__Options__Log": "ApplicationInsights",
+ "Settings__Options__DataStorage": "CosmosDb",
+ "Settings__Options__KeyStorage": "KeyVault",
+ "Settings__Options__Cache": "Memory",
+ "Settings__Options__DataCache": "None",
+ "Settings__MasterSeedEnabled": true,
+ "Settings__CosmosDb__EndpointUri": "[reference(concat('Microsoft.DocumentDb/databaseAccounts/', variables('foxidsDefaultName'))).documentEndpoint]",
+ "Settings__KeyVault__EndpointUri": "[reference(concat('Microsoft.KeyVault/vaults/', variables('foxidsDefaultName'))).vaultUri]",
+ "Settings__ApplicationInsights__WorkspaceId": "[reference(concat('microsoft.operationalinsights/workspaces/', variables('foxidsDefaultName'))).customerId]"
+ }
+ },
+ {
+ "type": "Microsoft.KeyVault/vaults/secrets",
+ "apiVersion": "2016-10-01",
+ "name": "[concat(variables('foxidsDefaultName'), '/Settings--CosmosDb--PrimaryKey')]",
+ "dependsOn": [
+ "[concat('Microsoft.KeyVault/vaults/', variables('foxidsDefaultName'))]",
+ "[concat('Microsoft.DocumentDB/databaseAccounts/', variables('foxidsDefaultName'))]"
+ ],
+ "properties": {
+ "value": "[listKeys(resourceId('Microsoft.DocumentDB/databaseAccounts', variables('foxidsDefaultName')), '2015-11-06').primaryMasterKey]"
+ }
+ },
+ {
+ "type": "Microsoft.KeyVault/vaults/secrets",
+ "apiVersion": "2016-10-01",
+ "name": "[concat(variables('foxidsDefaultName'), '/Settings--Sendgrid--ApiKey')]",
+ "dependsOn": [
+ "[concat('Microsoft.KeyVault/vaults/', variables('foxidsDefaultName'))]"
+ ],
+ "properties": {
+ "value": "[parameters('sendgridApiKey')]"
+ }
+ },
+ {
+ "type": "Microsoft.Authorization/roleAssignments",
+ "apiVersion": "2018-09-01-preview",
+ "name": "[guid(uniqueString(variables('foxidsDefaultName'), 'read', variables('foxidsControlSiteName')))]",
+ "scope": "[format('microsoft.operationalinsights/workspaces/{0}', variables('foxidsDefaultName'))]",
+ "properties": {
+ "roleDefinitionId": "[resourceId('Microsoft.Authorization/roleDefinitions', 'acdd72a7-3385-48ef-bd42-f606fba81ae7')]",
+ "principalId": "[reference(concat('Microsoft.Web/sites/', variables('foxidsControlSiteName')), '2018-02-01', 'Full').identity.principalId]",
+ "principalType": "ServicePrincipal"
+ },
+ "dependsOn": [
+ "[resourceId('Microsoft.Web/sites', variables('foxidsControlSiteName'))]",
+ "[resourceId('microsoft.operationalinsights/workspaces', variables('foxidsDefaultName'))]"
+ ]
+ },
+ {
+ "type": "Microsoft.Network/virtualNetworks",
+ "apiVersion": "2022-01-01",
+ "name": "[variables('foxidsDefaultName')]",
+ "location": "[resourceGroup().location]",
+ "properties": {
+ "addressSpace": {
+ "addressPrefixes": [
+ "10.1.0.0/16"
+ ]
+ },
+ "subnets": [
+ {
+ "name": "subnet-data",
+ "properties": {
+ "addressPrefix": "10.1.0.0/24",
+ "serviceEndpoints": [
+ {
+ "service": "Microsoft.AzureCosmosDB",
+ "locations": [
+ "[resourceGroup().location]"
+ ]
+ },
+ {
+ "service": "Microsoft.KeyVault",
+ "locations": [
+ "[resourceGroup().location]"
+ ]
+ }
+ ],
+ "privateEndpointNetworkPolicies": "Disabled",
+ "privateLinkServiceNetworkPolicies": "Enabled"
+ },
+ "type": "Microsoft.Network/virtualNetworks/subnets"
+ }
+ ]
+ }
+ },
+ {
+ "type": "Microsoft.Network/virtualNetworks/subnets",
+ "apiVersion": "2022-01-01",
+ "name": "[concat(variables('foxidsDefaultName'), '/subnet-data')]",
+ "dependsOn": [
+ "[resourceId('Microsoft.Network/virtualNetworks', variables('foxidsDefaultName'))]"
+ ],
+ "properties": {
+ "addressPrefix": "10.1.0.0/24",
+ "serviceEndpoints": [
+ {
+ "service": "Microsoft.AzureCosmosDB",
+ "locations": [
+ "[resourceGroup().location]"
+ ]
+ },
+ {
+ "service": "Microsoft.KeyVault",
+ "locations": [
+ "[resourceGroup().location]"
+ ]
+ }
+ ],
+ "delegations": [
+ {
+ "name": "delegation",
+ "id": "[concat(resourceId('Microsoft.Network/virtualNetworks/subnets', variables('foxidsDefaultName'), 'subnet-data'), '/delegations/delegation')]",
+ "properties": {
+ "serviceName": "Microsoft.Web/serverfarms"
+ },
+ "type": "Microsoft.Network/virtualNetworks/subnets/delegations"
+ }
+ ],
+ "privateEndpointNetworkPolicies": "Enabled",
+ "privateLinkServiceNetworkPolicies": "Enabled"
+ }
+ },
+ {
+ "type": "microsoft.insights/privatelinkscopes",
+ "apiVersion": "2021-07-01-preview",
+ "name": "[variables('foxidsDefaultName')]",
+ "location": "global",
+ "properties": {
+ "accessModeSettings": {
+ "exclusions": [],
+ "queryAccessMode": "PrivateOnly",
+ "ingestionAccessMode": "PrivateOnly"
+ }
+ }
+ },
+ {
+ "type": "microsoft.insights/privatelinkscopes/scopedresources",
+ "apiVersion": "2021-07-01-preview",
+ "name": "[concat(variables('foxidsDefaultName'), '/scoped-', variables('foxidsDefaultName'), '-insights')]",
+ "dependsOn": [
+ "[resourceId('microsoft.insights/privatelinkscopes', variables('foxidsDefaultName'))]",
+ "[resourceId('microsoft.insights/components', variables('foxidsDefaultName'))]"
+ ],
+ "properties": {
+ "linkedResourceId": "[resourceId('microsoft.insights/components', variables('foxidsDefaultName'))]"
+ }
+ },
+ {
+ "type": "microsoft.insights/privatelinkscopes/scopedresources",
+ "apiVersion": "2021-07-01-preview",
+ "name": "[concat(variables('foxidsDefaultName'), '/scoped-', variables('foxidsDefaultName'), '-workspaces')]",
+ "dependsOn": [
+ "[resourceId('microsoft.insights/privatelinkscopes', variables('foxidsDefaultName'))]",
+ "[resourceId('microsoft.operationalinsights/workspaces', variables('foxidsDefaultName'))]"
+ ],
+ "properties": {
+ "linkedResourceId": "[resourceId('microsoft.operationalinsights/workspaces', variables('foxidsDefaultName'))]"
+ }
+ },
+ {
+ "type": "Microsoft.Network/privateDnsZones",
+ "apiVersion": "2018-09-01",
+ "name": "privatelink.oms.opinsights.azure.com",
+ "location": "global",
+ "properties": {
+ "maxNumberOfRecordSets": 25000,
+ "maxNumberOfVirtualNetworkLinks": 1000,
+ "maxNumberOfVirtualNetworkLinksWithRegistration": 100
+ }
+ },
+ {
+ "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks",
+ "apiVersion": "2018-09-01",
+ "name": "[concat('privatelink.oms.opinsights.azure.com', '/link_to_', variables('foxidsDefaultName'))]",
+ "location": "global",
+ "dependsOn": [
+ "[resourceId('Microsoft.Network/privateDnsZones', 'privatelink.oms.opinsights.azure.com')]",
+ "[resourceId('Microsoft.Network/virtualNetworks', variables('foxidsDefaultName'))]"
+ ],
+ "properties": {
+ "registrationEnabled": false,
+ "virtualNetwork": {
+ "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('foxidsDefaultName'))]"
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Network/privateDnsZones",
+ "apiVersion": "2018-09-01",
+ "name": "privatelink.ods.opinsights.azure.com",
+ "location": "global",
+ "properties": {
+ "maxNumberOfRecordSets": 25000,
+ "maxNumberOfVirtualNetworkLinks": 1000,
+ "maxNumberOfVirtualNetworkLinksWithRegistration": 100
+ }
+ },
+ {
+ "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks",
+ "apiVersion": "2018-09-01",
+ "name": "[concat('privatelink.ods.opinsights.azure.com', '/link_to_', variables('foxidsDefaultName'))]",
+ "location": "global",
+ "dependsOn": [
+ "[resourceId('Microsoft.Network/privateDnsZones', 'privatelink.ods.opinsights.azure.com')]",
+ "[resourceId('Microsoft.Network/virtualNetworks', variables('foxidsDefaultName'))]"
+ ],
+ "properties": {
+ "registrationEnabled": false,
+ "virtualNetwork": {
+ "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('foxidsDefaultName'))]"
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Network/privateDnsZones",
+ "apiVersion": "2018-09-01",
+ "name": "privatelink.agentsvc.azure-automation.net",
+ "location": "global",
+ "properties": {
+ "maxNumberOfRecordSets": 25000,
+ "maxNumberOfVirtualNetworkLinks": 1000,
+ "maxNumberOfVirtualNetworkLinksWithRegistration": 100
+ }
+ },
+ {
+ "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks",
+ "apiVersion": "2018-09-01",
+ "name": "[concat('privatelink.agentsvc.azure-automation.net', '/link_to_', variables('foxidsDefaultName'))]",
+ "location": "global",
+ "dependsOn": [
+ "[resourceId('Microsoft.Network/privateDnsZones', 'privatelink.agentsvc.azure-automation.net')]",
+ "[resourceId('Microsoft.Network/virtualNetworks', variables('foxidsDefaultName'))]"
+ ],
+ "properties": {
+ "registrationEnabled": false,
+ "virtualNetwork": {
+ "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('foxidsDefaultName'))]"
+ }
+ }
+ },
+ {
+ "type": "Microsoft.Network/privateDnsZones",
+ "apiVersion": "2018-09-01",
+ "name": "privatelink.blob.core.windows.net",
+ "location": "global",
+ "properties": {
+ "maxNumberOfRecordSets": 25000,
+ "maxNumberOfVirtualNetworkLinks": 1000,
+ "maxNumberOfVirtualNetworkLinksWithRegistration": 100
+ }
+ },
+ {
+ "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks",
+ "apiVersion": "2018-09-01",
+ "name": "[concat('privatelink.blob.core.windows.net', '/link_to_', variables('foxidsDefaultName'))]",
+ "location": "global",
+ "dependsOn": [
+ "[resourceId('Microsoft.Network/privateDnsZones', 'privatelink.blob.core.windows.net')]",
+ "[resourceId('Microsoft.Network/virtualNetworks', variables('foxidsDefaultName'))]"
+ ],
+ "properties": {
+ "registrationEnabled": false,
+ "virtualNetwork": {
+ "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('foxidsDefaultName'))]"
+ }
+ }
+ }
+ ]
+}
\ No newline at end of file
diff --git a/azuredeploy.json b/azuredeploy.json
index 1c40beff9..73a23386d 100644
--- a/azuredeploy.json
+++ b/azuredeploy.json
@@ -3,10 +3,9 @@
"contentVersion": "1.0.0.0",
"parameters": {
"appServicePlanSize": {
- "defaultValue": "P1V2",
+ "defaultValue": "P1V3",
"allowedValues": [
"F1",
- "D1",
"B1",
"B2",
"B3",
@@ -16,12 +15,10 @@
"P1V2",
"P2V2",
"P3V2",
+ "P0V3",
"P1V3",
"P2V3",
- "P3V3",
- "P1",
- "P2",
- "P3"
+ "P3V3"
],
"type": "string",
"metadata": {
diff --git a/docs/app-reg-oidc.md b/docs/app-reg-oidc.md
index 6ab2f3245..b7b2ae781 100644
--- a/docs/app-reg-oidc.md
+++ b/docs/app-reg-oidc.md
@@ -8,10 +8,10 @@ Your application become a Relying Party (RP) and FoxIDs acts as an OpenID Provid
FoxIDs support [OpenID Connect Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html) where your application can discover the OpenID Provider.
-FoxIDs support [OpenID Connect authentication](https://openid.net/specs/openid-connect-core-1_0.html#Authentication) (login), [RP-initiated logout](https://openid.net/specs/openid-connect-rpinitiated-1_0.html) and [front-channel logout](https://openid.net/specs/openid-connect-frontchannel-1_0.html). A session is established when the user authenticates and the session id is included in the id token. The session is invalidated on logout.
+FoxIDs support [OpenID Connect authentication](https://openid.net/specs/openid-connect-core-1_0.html#Authentication) (login), [RP-initiated logout](https://openid.net/specs/openid-connect-rpinitiated-1_0.html) and [front-channel logout](https://openid.net/specs/openid-connect-frontchannel-1_0.html). A session is established when the user authenticates and the session id is included in the ID token. The session is invalidated on logout.
FoxIDs can show a logout confirmation dialog depending on configuration and rather an ID token is included in the logout request or not.
-Default both id token and access token are issued with the client's client id as the audience. The default resource can be removed from the access token in FoxIDs Control.
+Default both ID token and access token are issued with the client's client id as the audience. The default resource can be removed from the access token in FoxIDs Control.
Access tokens can be issued with a list of audiences and thereby be issued to multiple APIs defined in FoxIDs as [OAuth 2.0 resources](app-reg-oauth-2.0.md#oauth-20-resource).
The application can then call an API securing the call with the access token using the [The OAuth 2.0 Authorization Framework: Bearer Token Usage](https://datatracker.ietf.org/doc/html/rfc6750).
diff --git a/docs/bridge.md b/docs/bridge.md
index 5325e8c8c..bb25f2bc8 100644
--- a/docs/bridge.md
+++ b/docs/bridge.md
@@ -8,7 +8,7 @@ A log in request from your app is routed as an external SAML 2.0 log in requests
![Bridge SAML 2.0 to OpenID Connect](images/bridge-saml-oidc.svg)
The opposite is likewise possible starting the log in request from a [SAML 2.0 application registration](app-reg-saml-2.0.md) app and routing to an external OpenID Provider (OP) configured as a [OpenID Connect authentication method](auth-method-oidc.md).
-Subsequently, the response is mapped to a SAML 2.0 response.
+Subsequently, the response is converted to a SAML 2.0 response.
![Bridge OpenID Connect to SAML 2.0](images/bridge-oidc-saml.svg)
diff --git a/docs/control.md b/docs/control.md
index c1b0e6329..c500b0ea8 100644
--- a/docs/control.md
+++ b/docs/control.md
@@ -60,46 +60,45 @@ The environment properties can be configured by clicking the top right setting i
![Configure environment settings](images/configure-environment-setting.png)
## FoxIDs Control API
-Control API is a REST API and has a Swagger (OpenApi) interface description.
+Control API is a REST API and has a [Swagger (OpenApi)](https://control.foxids.com/api/swagger/v1/swagger.json) interface description.
-Control API require that the client calling the API is granted the `foxids:master` scope to access master tenant data or the `foxids:tenant` scope to access tenant data in a particular tenant. Normally only tenant data is accessed.
+Control API require that the client calling the API is granted appropriate [access rights](#api-access-rights) by scopes and roles.
- - The API can be accessed with a OAuth 2.0 client. Where the client is granted the administrator role `foxids:tenant.admin` acting as the client itself using client credentials grant.
- It is probably helpful to take a look at how the [sample seed tool](samples.md#configure-the-sample-seed-tool) client is granted access.
- - Or the API can be accessed with a OpenID Connect client with an authenticated master environment user. Where the user is granted the administrator role `foxids:tenant.admin`.
- *As an advanced option the mater user can also be granted access via a trust.*
-
-This shows the Control API configuration in a tenants master environment with a scope that grants access to tenant data.
+This shows the Control API configuration in a tenants master environment with a default set of scopes that grants access to tenants data.
![Configure foxids_control_api](images/configure-foxids_control_api.png)
+More scopes can be added to extend the [API access rights](#api-access-rights) for the different environments. To achieve least privileges access rights for each environment.
+
Control API is called with an access token as described in the [OAuth 2.0 Bearer Token (RFC 6750)](https://datatracker.ietf.org/doc/html/rfc6750) standard.
-The Swagger (OpenApi) interface document is exposed on `.../api/swagger/v1/swagger.json`.
+If you host FoxIDs your self the Swagger (OpenApi) interface document is exposed in FoxIDs Control on `.../api/swagger/v1/swagger.json`.
> FoxIDs.com Swagger (OpenApi) [https://control.foxids.com/api/swagger/v1/swagger.json](https://control.foxids.com/api/swagger/v1/swagger.json)
The Control API URL contains the tenant name and environment name on winch you want to operate `.../[tenant_name]/[environment_name]/...`.
To call the API you replace the `[tenant_name]` element with your tenant name and the `[environment_name]` element with the environment name of the environment you want to call.
-If you e.g. want read a OpenID Connect application registration on FoxIDs.com with the name `some_oidc_app` you do a HTTP GET call to `https://control.foxids.com/api/[tenant_name]/[environment_name]/!oidcdownparty?name=some_oidc_app` - replaced with your tenant and environment names.
+If you e.g. want to read a OpenID Connect application registration on FoxIDs.com with the name `some_oidc_app` you do a HTTP GET call to `https://control.foxids.com/api/[tenant_name]/[environment_name]/!oidcdownparty?name=some_oidc_app` - replaced with your tenant and environment names.
### API access rights
Access to Control API is limited by scopes and roles. There are two sets of scopes based on `foxids:master` which grant access to the master tenant data and `foxids:tenant` which grant access to tenant data.
-The Control API resource `foxids_control_api` is defined in each tenant's master environment and the configured set of scopes grant access the tenants data in the Control API.
+The Control API resource `foxids_control_api` is defined in each tenant's master environment and the configured set of scopes grant access the tenants data through the Control API.
-A scopes access is limited by adding more elements separated with semicolon and dot. The dot notation limits or grant a sub role, the notation is both used in scopes and roles.
+A scopes access is limited by adding more elements separated with semicolon and dot. The dot notation limits to a specific sub role, the notation is both used in scopes and roles.
To be granted access the caller is required to possess one or more matching scope(s) and role(s).
Each access right is both defined as a scope and a role. This makes it possible to limit or grant access on both client and user level. The access rights are a hierarchy and the client and user do not need to be granted matching scopes and roles.
-The administrator role `foxids:tenant.admin` grants access to all data in a tenant and the master tenant data, it is the same as having the role `foxids:tenant` and `foxids:master`.
+The administrator role `foxids:tenant.admin` grants access to all data in a tenant and the master tenant data, it is the same as having the roles `foxids:tenant` and `foxids:master`.
> A client request a scope by requesting a scope on a resource, separating the resource and scope with a semicolon. E.g., to request the `foxids:tenant:track:party.create` scope the client request for `foxids_control_api:foxids:tenant:track:party.create`.
#### Tenant access rights
The tenant access rights is at the same time both scopes and roles.
+> If the scope you need is not defined on the Control API `foxids_control_api` you can add the scope. The same goes for roles which has to be defined on the user or the calling client.
+
The `:track[xxxx]` specifies a tenant e.g., the `dev` tenant is `:track[dev]`.
@@ -395,5 +394,3 @@ The master tenant access rights is at the same time both scopes and roles.
read |
-
-If the scope you need is not defined on the Control API `foxids_control_api` you can add the scope. The same goes for roles which has to be defined on the user or the calling client.
\ No newline at end of file
diff --git a/docs/deployment-k8s.md b/docs/deployment-k8s.md
index 62e122c09..ccacb691f 100644
--- a/docs/deployment-k8s.md
+++ b/docs/deployment-k8s.md
@@ -7,9 +7,9 @@ This is a description of how to make a default [deployment](#deployment), [log i
Pre requirements:
- You have a Kubernetes cluster or Docker Desktop with Kubernetes enabled.
- You have basic knowledge about Kubernetes.
-- You have `kubectl` installer on your workstation.
-- You have [Helm](https://docs.helm.sh/) installer on your workstation and your cluster.
- Install Helm on windows with this CMD command `winget install Helm.Helm`
+- You have `kubectl` installer.
+- You have [Helm](https://docs.helm.sh/) installer.
+ *Install Helm on windows with this CMD command `winget install Helm.Helm`*
> This is a list of [useful commands](#useful-commands) in the end of this description.
diff --git a/docs/howto-connect.md b/docs/howto-connect.md
index 27d02c091..7a2f458a4 100644
--- a/docs/howto-connect.md
+++ b/docs/howto-connect.md
@@ -36,6 +36,8 @@ All IdPs supporting either OpenID Connect or SAML 2.0 can be connected to FoxIDs
Configure [OpenID Connect](auth-method-oidc.md) which trust an external OpenID Provider (OP) - *an Identity Provider (IdP) is called an OpenID Provider (OP) if configured with OpenID Connect*.
+> You should always ask for the `sub` claim, even if you use the `email` claim or e.g. another custom user ID claim.
+
How to guides:
- Connect [IdentityServer](auth-method-howto-oidc-identityserver.md)
@@ -50,6 +52,9 @@ How to guides:
Configure [SAML 2.0](auth-method-saml-2.0.md) which trust an external Identity Provider (IdP).
+> You should always ask for the `NameID` claim, even if you use the email (`http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress`) claim or e.g. another custom user ID claim. SAML 2.0 can not do logout without the `NameID` claim.
+> You should prefer to do SAML 2.0 connects with the use of the authentication methods metadata, then the customer's IdP can automatically download the certificate(s). And request for an online IdP metadata from the customer.
+
How to guides:
- Connect [PingIdentity / PingOne](auth-method-howto-saml-2.0-pingone.md)
diff --git a/docs/howto-saml-2.0-context-handler.md b/docs/howto-saml-2.0-context-handler.md
index 1ee3df80a..214a06679 100644
--- a/docs/howto-saml-2.0-context-handler.md
+++ b/docs/howto-saml-2.0-context-handler.md
@@ -228,22 +228,20 @@ Create the claims which has to be issued to Context Handler in claim transforms.
2. Select the **Claim transforms** tab
3. Add a **Constant** claim `https://data.gov.dk/model/core/specVersion` with the value `OIO-SAML-3.0`
4. Add a **Constant** claim `https://data.gov.dk/model/core/kombitSpecVer` with the value `2.0`
-
-5. Add the spec. ver. `https://data.gov.dk/model/core/specVersion` and Kombit spec. ver. `https://data.gov.dk/model/core/kombitSpecVer` as constant claims
-6. Add a **Constant** levels of assurance (loa) claim `https://data.gov.dk/concept/core/nsis/loa` with e.g. the value `Substantial` or read the claim through the claims pipeline
+5. Add a **Constant** levels of assurance (loa) claim `https://data.gov.dk/concept/core/nsis/loa` with e.g. the value `Substantial` or read the claim through the claims pipeline
![Context Handler SAML 2.0 application registration](images/howto-saml-context-handler-app-ct1.png)
-7. Add a **Concatenated** claim to replace the NameID `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier` claim which a concatenated version of the CVR number, display name and unique user ID
-8. Select **Action** **Replace claim**
-9. Concatenate claims:
+6. Add a **Concatenated** claim to replace the NameID `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier` claim which a concatenated version of the CVR number, display name and unique user ID
+7. Select **Action** **Replace claim**
+8. Concatenate claims:
- `https://data.gov.dk/model/core/eid/professional/cvr`
- `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname`
- `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname`
- `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier`
-10. Set the **Concatenate format string** to `C=DK,O={0},CN={1} {2},Serial={3}`
+9. Set the **Concatenate format string** to `C=DK,O={0},CN={1} {2},Serial={3}`
![Context Handler SAML 2.0 application registration](images/howto-saml-context-handler-app-ct2.png)
-11. Click **Update**
+10. Click **Update**
**4 - Add SAML 2.0 claim to JWT claim mappings in [FoxIDs Control Client](control.md#foxids-control-client)**
diff --git a/docs/images/configure-foxids_control_api.png b/docs/images/configure-foxids_control_api.png
index d0f943a69..1942237de 100644
Binary files a/docs/images/configure-foxids_control_api.png and b/docs/images/configure-foxids_control_api.png differ
diff --git a/docs/name-title-icon-css.md b/docs/name-title-icon-css.md
index a87505b04..673e2cc12 100644
--- a/docs/name-title-icon-css.md
+++ b/docs/name-title-icon-css.md
@@ -12,7 +12,9 @@ The name is configured in the environment settings in [FoxIDs Control Client](co
## Add browser title, browser icon and CSS
-The FoxIDs user interface can be customized per [login authentication method](login). This means that a single FoxIDs environment can support multiple user interface designs with different browser titles, browser icons and CSS.
+The FoxIDs user interface can be customised per [login authentication method](login). This means that a single FoxIDs environment can support multiple user interface designs with different browser titles, browser icons and CSS.
+
+If you do not specify a login authentication method as an allowed authentication method in your application. The default login authentication method is used, and also whatever is customised to it.
> FoxIDs use Bootstrap 4.6 and Flexbox CSS.
diff --git a/src/FoxIDs.Control/Controllers/Base/ApiController.cs b/src/FoxIDs.Control/Controllers/Base/ApiController.cs
index d8b313843..ccdc4b970 100644
--- a/src/FoxIDs.Control/Controllers/Base/ApiController.cs
+++ b/src/FoxIDs.Control/Controllers/Base/ApiController.cs
@@ -9,6 +9,7 @@
using Microsoft.AspNetCore.Routing;
using System;
using System.Linq;
+using System.Runtime.CompilerServices;
namespace FoxIDs.Controllers
{
@@ -49,7 +50,7 @@ public virtual CreatedResult Created(object queryValues, object value)
return new CreatedResult(uriBuilder.Uri, value);
}
- public virtual NotFoundObjectResult NotFound(string typeName, string name, string fieldName = null)
+ public virtual NotFoundObjectResult NotFound(string typeName, string name, string fieldName = null, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
{
try
{
@@ -57,7 +58,7 @@ public virtual NotFoundObjectResult NotFound(string typeName, string name, strin
}
catch (Exception ex)
{
- logger.Warning(ex);
+ logger.Warning(ex, GetMessage("Not found", memberName, sourceFilePath, sourceLineNumber));
if (!fieldName.IsNullOrWhiteSpace())
{
Response.Headers["field"] = fieldName;
@@ -66,7 +67,7 @@ public virtual NotFoundObjectResult NotFound(string typeName, string name, strin
}
}
- public virtual ConflictObjectResult Conflict(string typeName, string name, string fieldName = null)
+ public virtual ConflictObjectResult Conflict(string typeName, string name, string fieldName = null, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
{
try
{
@@ -74,7 +75,7 @@ public virtual ConflictObjectResult Conflict(string typeName, string name, strin
}
catch (Exception ex)
{
- logger.Warning(ex);
+ logger.Warning(ex, GetMessage("Conflict", memberName, sourceFilePath, sourceLineNumber));
if (!fieldName.IsNullOrWhiteSpace())
{
Response.Headers["field"] = fieldName;
@@ -83,7 +84,7 @@ public virtual ConflictObjectResult Conflict(string typeName, string name, strin
}
}
- public BadRequestObjectResult BadRequest(ModelStateDictionary modelState, Exception innerEx = null)
+ public BadRequestObjectResult BadRequest(ModelStateDictionary modelState, Exception innerEx = null, [CallerMemberName] string memberName = "", [CallerFilePath] string sourceFilePath = "", [CallerLineNumber] int sourceLineNumber = 0)
{
try
{
@@ -92,7 +93,7 @@ public BadRequestObjectResult BadRequest(ModelStateDictionary modelState, Except
}
catch (Exception ex)
{
- logger.Error(ex);
+ logger.Error(ex, GetMessage("Bad request", memberName, sourceFilePath, sourceLineNumber));
if (modelState.Keys.Count() == 1)
{
Response.Headers["field"] = modelState.Keys.First();
@@ -100,5 +101,10 @@ public BadRequestObjectResult BadRequest(ModelStateDictionary modelState, Except
return base.BadRequest(ex.Message);
}
}
+
+ private string GetMessage(string message, string memberName, string sourceFilePath, int sourceLineNumber)
+ {
+ return $"{message} at {memberName} in {sourceFilePath}:line {sourceLineNumber}";
+ }
}
}
diff --git a/src/FoxIDs.Control/Controllers/Client/MClientSettingsController.cs b/src/FoxIDs.Control/Controllers/Client/MClientSettingsController.cs
index 4e33e0694..6b77dca58 100644
--- a/src/FoxIDs.Control/Controllers/Client/MClientSettingsController.cs
+++ b/src/FoxIDs.Control/Controllers/Client/MClientSettingsController.cs
@@ -33,7 +33,10 @@ public MClientSettingsController(FoxIDsControlSettings settings, TelemetryScoped
Version = version.ToString(2),
FullVersion = version.ToString(3),
LogOption = mapper.Map(settings.Options.Log),
- KeyStorageOption = mapper.Map(settings.Options.KeyStorage)
+ KeyStorageOption = mapper.Map(settings.Options.KeyStorage),
+ EnablePayment = settings.Payment?.EnablePayment == true && settings.Usage?.EnableInvoice == true,
+ PaymentTestMode = settings.Payment != null ? settings.Payment.TestMode : true,
+ MollieProfileId = settings.Payment?.MollieProfileId,
});
}
}
diff --git a/src/FoxIDs.Control/Controllers/Client/WController.cs b/src/FoxIDs.Control/Controllers/Client/WController.cs
index 6c6278e2f..dd853d8d6 100644
--- a/src/FoxIDs.Control/Controllers/Client/WController.cs
+++ b/src/FoxIDs.Control/Controllers/Client/WController.cs
@@ -8,6 +8,7 @@
using FoxIDs.Models;
using FoxIDs.Infrastructure;
using Microsoft.AspNetCore.Http;
+using FoxIDs.Models.Config;
namespace FoxIDs.Controllers.Client
{
@@ -16,11 +17,13 @@ public class WController : Controller
private static string indexFile;
private readonly TelemetryScopedLogger logger;
private readonly IWebHostEnvironment currentEnvironment;
+ private readonly FoxIDsControlSettings settings;
- public WController(TelemetryScopedLogger logger, IWebHostEnvironment environment)
+ public WController(TelemetryScopedLogger logger, IWebHostEnvironment environment, FoxIDsControlSettings settings)
{
this.logger = logger;
currentEnvironment = environment;
+ this.settings = settings;
}
[ResponseCache(Location = ResponseCacheLocation.None, NoStore = true)]
@@ -83,6 +86,14 @@ private IActionResult GetProcessedIndexFile(string technicalError = null)
var file = currentEnvironment.WebRootFileProvider.GetFileInfo("index.html");
indexFile = System.IO.File.ReadAllText(file.PhysicalPath);
indexFile = indexFile.Replace("{version}", GetBuildDate());
+ if(settings.Payment?.EnablePayment == true && settings.Usage?.EnableInvoice == true)
+ {
+ indexFile = indexFile.Replace("{payment_script}", "");
+ }
+ else
+ {
+ indexFile = indexFile.Replace("{payment_script}", string.Empty);
+ }
}
return Content(AddErrorInfo(indexFile, technicalError), "text/HTML");
}
diff --git a/src/FoxIDs.Control/Controllers/Helpers/TDownPartyTestController.cs b/src/FoxIDs.Control/Controllers/Helpers/TDownPartyTestController.cs
index 2d179711b..9702d9eef 100644
--- a/src/FoxIDs.Control/Controllers/Helpers/TDownPartyTestController.cs
+++ b/src/FoxIDs.Control/Controllers/Helpers/TDownPartyTestController.cs
@@ -101,6 +101,7 @@ public TDownPartyTestController(FoxIDsControlSettings settings, TelemetryScopedL
IsTest = true,
TestUrl = testUrl,
TestExpireAt = DateTimeOffset.UtcNow.AddSeconds(settings.DownPartyTestLifetime).ToUnixTimeSeconds(),
+ TestExpireInSeconds = settings.DownPartyTestLifetime,
Nonce = authenticationRequest.Nonce,
CodeVerifier = codeVerifier,
AllowUpParties = testDownPartyRequest.UpParties.Select(p => new UpPartyLink { Name = p.Name.ToLower(), ProfileName = p.ProfileName?.ToLower() }).ToList(),
@@ -151,6 +152,7 @@ public TDownPartyTestController(FoxIDsControlSettings settings, TelemetryScopedL
DisplayName = mParty.DisplayName,
TestUrl = testUrl,
TestExpireAt = mParty.TestExpireAt.Value,
+ TestExpireInSeconds = mParty.TestExpireInSeconds.Value,
});
}
catch (ValidationException)
diff --git a/src/FoxIDs.Control/Controllers/Helpers/TPlanInfoController.cs b/src/FoxIDs.Control/Controllers/Helpers/TPlanInfoController.cs
new file mode 100644
index 000000000..3e2a1bf63
--- /dev/null
+++ b/src/FoxIDs.Control/Controllers/Helpers/TPlanInfoController.cs
@@ -0,0 +1,54 @@
+using FoxIDs.Infrastructure;
+using Api = FoxIDs.Models.Api;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using System.Threading.Tasks;
+using FoxIDs.Infrastructure.Security;
+using FoxIDs.Repository;
+using FoxIDs.Models;
+using AutoMapper;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace FoxIDs.Controllers
+{
+ [TenantScopeAuthorize(Constants.ControlApi.Segment.Base, Constants.ControlApi.Segment.Party)]
+ public class TPlanInfoController : ApiController
+ {
+ private readonly IMapper mapper;
+ private readonly IMasterDataRepository masterDataRepository;
+
+ public TPlanInfoController(TelemetryScopedLogger logger, IMapper mapper, IMasterDataRepository masterDataRepository) : base(logger)
+ {
+ this.mapper = mapper;
+ this.masterDataRepository = masterDataRepository;
+ }
+
+ ///
+ /// Get list of plans.
+ ///
+ /// Client settings.
+ [ProducesResponseType(typeof(HashSet), StatusCodes.Status200OK)]
+ public async Task>> GetPlanInfo()
+ {
+ try
+ {
+ var mPlans = await masterDataRepository.GetListAsync();
+ var aPlans = new HashSet(mPlans.Count());
+ foreach (var mPlan in mPlans.OrderBy(p => p.CostPerMonth).ThenBy(t => t.Name))
+ {
+ aPlans.Add(mapper.Map(mPlan));
+ }
+ return Ok(aPlans);
+ }
+ catch (FoxIDsDataException ex)
+ {
+ if (ex.StatusCode == DataStatusCode.NotFound)
+ {
+ return Ok(new HashSet());
+ }
+ throw;
+ }
+ }
+ }
+}
diff --git a/src/FoxIDs.Control/Controllers/Master/MPlanController.cs b/src/FoxIDs.Control/Controllers/Master/MPlanController.cs
index 91989d1b3..c162a72e1 100644
--- a/src/FoxIDs.Control/Controllers/Master/MPlanController.cs
+++ b/src/FoxIDs.Control/Controllers/Master/MPlanController.cs
@@ -4,7 +4,6 @@
using FoxIDs.Repository;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
-using System.Net;
using System.Threading.Tasks;
using AutoMapper;
using System.Linq;
@@ -77,11 +76,15 @@ public MPlanController(TelemetryScopedLogger logger, IMapper mapper, ITenantData
if (!await ModelState.TryValidateObjectAsync(plan)) return BadRequest(ModelState);
plan.Name = plan.Name.ToLower();
+ var count = await masterDataRepository.CountAsync();
+ if (count >= Constants.Models.Plan.PlansMax)
+ {
+ throw new Exception($"Maximum number of plans ({Constants.Models.Plan.PlansMax}) has been reached.");
+ }
+
var mPlan = mapper.Map(plan);
await masterDataRepository.CreateAsync(mPlan);
- await planCacheLogic.InvalidatePlanCacheAsync(plan.Name);
-
return Created(mapper.Map(mPlan));
}
catch (FoxIDsDataException ex)
diff --git a/src/FoxIDs.Control/Controllers/Master/MUsageSettingsController.cs b/src/FoxIDs.Control/Controllers/Master/MUsageSettingsController.cs
new file mode 100644
index 000000000..9a1e4b55b
--- /dev/null
+++ b/src/FoxIDs.Control/Controllers/Master/MUsageSettingsController.cs
@@ -0,0 +1,106 @@
+using FoxIDs.Infrastructure;
+using FoxIDs.Models;
+using Api = FoxIDs.Models.Api;
+using FoxIDs.Repository;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using System.Threading.Tasks;
+using AutoMapper;
+using FoxIDs.Infrastructure.Filters;
+using FoxIDs.Infrastructure.Security;
+using System.Collections.Generic;
+
+namespace FoxIDs.Controllers
+{
+ [RequireMasterTenant]
+ [MasterScopeAuthorize]
+ public class MUsageSettingsController : ApiController
+ {
+ private readonly TelemetryScopedLogger logger;
+ private readonly IMapper mapper;
+ private readonly IMasterDataRepository masterDataRepository;
+
+ public MUsageSettingsController(TelemetryScopedLogger logger, IMapper mapper, IMasterDataRepository masterDataRepository) : base(logger)
+ {
+ this.logger = logger;
+ this.mapper = mapper;
+ this.masterDataRepository = masterDataRepository;
+ }
+
+ ///
+ /// Get usage settings.
+ ///
+ /// Usage settings.
+ [ProducesResponseType(typeof(Api.UsageSettings), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task> GetUsageSettings()
+ {
+ try
+ {
+ var mUsageSettings = await LoadAndCreateUsageSettings();
+ return Ok(mapper.Map(mUsageSettings));
+ }
+ catch (FoxIDsDataException ex)
+ {
+ if (ex.StatusCode == DataStatusCode.NotFound)
+ {
+ logger.Warning(ex, $"NotFound, Get '{typeof(Api.UsageSettings).Name}'.");
+ return NotFound(typeof(Api.UsageSettings).Name, "default");
+ }
+ throw;
+ }
+ }
+
+ ///
+ /// Update usage settings.
+ ///
+ /// Usage settings.
+ /// UsageSettings.
+ [ProducesResponseType(typeof(Api.UsageSettings), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task> PutUsageSettings([FromBody] Api.UsageSettings usageSettings)
+ {
+ try
+ {
+ if (!await ModelState.TryValidateObjectAsync(usageSettings)) return BadRequest(ModelState);
+
+ var mUsageSettings = await LoadAndCreateUsageSettings();
+ mUsageSettings.CurrencyExchanges = mapper.Map>(usageSettings.CurrencyExchanges);
+ foreach (var currencyExchange in mUsageSettings.CurrencyExchanges)
+ {
+ currencyExchange.Currency = currencyExchange.Currency.ToUpper();
+ }
+ mUsageSettings.HourPrice = usageSettings.HourPrice;
+ mUsageSettings.InvoiceNumber = usageSettings.InvoiceNumber;
+ mUsageSettings.InvoiceNumberPrefix = usageSettings.InvoiceNumberPrefix;
+ await masterDataRepository.UpdateAsync(mUsageSettings);
+
+ return Ok(mapper.Map(mUsageSettings));
+ }
+ catch (FoxIDsDataException ex)
+ {
+ if (ex.StatusCode == DataStatusCode.NotFound)
+ {
+ logger.Warning(ex, $"NotFound, Update '{typeof(Api.UsageSettings).Name}'.");
+ return NotFound(typeof(Api.UsageSettings).Name, "default");
+ }
+ throw;
+ }
+ }
+
+ private async Task LoadAndCreateUsageSettings()
+ {
+ var mUsageSettings = await masterDataRepository.GetAsync(await UsageSettings.IdFormatAsync(), required: false);
+ if (mUsageSettings == null)
+ {
+ mUsageSettings = new UsageSettings
+ {
+ Id = await UsageSettings.IdFormatAsync()
+ };
+ await masterDataRepository.CreateAsync(mUsageSettings);
+ }
+
+ return mUsageSettings;
+ }
+ }
+}
diff --git a/src/FoxIDs.Control/Controllers/Parties/GenericPartyApiController.cs b/src/FoxIDs.Control/Controllers/Parties/GenericPartyApiController.cs
index a484aea48..6c3b18cbb 100644
--- a/src/FoxIDs.Control/Controllers/Parties/GenericPartyApiController.cs
+++ b/src/FoxIDs.Control/Controllers/Parties/GenericPartyApiController.cs
@@ -12,6 +12,7 @@
using FoxIDs.Models.Config;
using System.Collections.Generic;
using System.Linq;
+using FoxIDs.Logic.Queues;
namespace FoxIDs.Controllers
{
@@ -205,7 +206,27 @@ protected async Task> Put(AParty party, Func 0)
+ {
+ mOidcDownParty.TestExpireInSeconds = downPartyTestLifetime;
+ var newTestExpireAt = DateTimeOffset.UtcNow.AddSeconds(downPartyTestLifetime).ToUnixTimeSeconds();
+ if (newTestExpireAt > tempMParty.TestExpireAt)
+ {
+ mOidcDownParty.TestExpireAt = newTestExpireAt;
+ }
+ else
+ {
+ mOidcDownParty.TestExpireAt = tempMParty.TestExpireAt;
+ }
+ }
+ else
+ {
+ mOidcDownParty.TestExpireInSeconds = 0;
+ mOidcDownParty.TestExpireAt = -1;
+ }
+
mOidcDownParty.CodeVerifier = tempMParty.CodeVerifier;
mOidcDownParty.Nonce = tempMParty.Nonce;
}
diff --git a/src/FoxIDs.Control/Controllers/Parties/TExternalLoginUpPartyController.cs b/src/FoxIDs.Control/Controllers/Parties/TExternalLoginUpPartyController.cs
index 5caa36867..d01e53c2a 100644
--- a/src/FoxIDs.Control/Controllers/Parties/TExternalLoginUpPartyController.cs
+++ b/src/FoxIDs.Control/Controllers/Parties/TExternalLoginUpPartyController.cs
@@ -8,6 +8,7 @@
using AutoMapper;
using FoxIDs.Logic;
using FoxIDs.Models.Config;
+using FoxIDs.Logic.Queues;
namespace FoxIDs.Controllers
{
diff --git a/src/FoxIDs.Control/Controllers/Parties/TFilterUpPartyController.cs b/src/FoxIDs.Control/Controllers/Parties/TFilterUpPartyController.cs
index a26f90486..d4c541717 100644
--- a/src/FoxIDs.Control/Controllers/Parties/TFilterUpPartyController.cs
+++ b/src/FoxIDs.Control/Controllers/Parties/TFilterUpPartyController.cs
@@ -6,7 +6,6 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
-using System.Net;
using System.Collections.Generic;
using System.Linq;
using ITfoxtec.Identity;
@@ -33,25 +32,18 @@ public TFilterUpPartyController(TelemetryScopedLogger logger, IMapper mapper, IT
///
/// Filter authentication method.
///
- /// Filter authentication method name.
+ /// Filter authentication method by name.
+ /// Filter authentication method by HRD domains.
/// Authentication methods.
[ProducesResponseType(typeof(HashSet), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task>> GetFilterUpParty(string filterName)
+ public async Task>> GetFilterUpParty(string filterName, string filterHrdDomains)
{
try
{
- var doFilterPartyType = Enum.TryParse(filterName, out var filterPartyType);
- var idKey = new Track.IdKey { TenantName = RouteBinding.TenantName, TrackName = RouteBinding.TrackName };
- (var mUpPartys, _) = filterName.IsNullOrWhiteSpace() ?
- await tenantDataRepository.GetListAsync>(idKey, whereQuery: p => p.DataType.Equals(dataType)) :
- await tenantDataRepository.GetListAsync>(idKey, whereQuery: p => p.DataType.Equals(dataType) &&
- (p.Name.Contains(filterName, StringComparison.CurrentCultureIgnoreCase) || p.DisplayName.Contains(filterName, StringComparison.CurrentCultureIgnoreCase) ||
- (p.Profiles != null && p.Profiles.Any(p => p.Name.Contains(filterName, StringComparison.CurrentCultureIgnoreCase) || p.DisplayName.Contains(filterName, StringComparison.CurrentCultureIgnoreCase))) ||
- (doFilterPartyType && p.Type == filterPartyType)));
-
+ (var mUpPartys, _) = await GetFilterUpPartyInternalAsync(filterName, filterHrdDomains);
var aUpPartys = new HashSet(mUpPartys.Count());
- foreach(var mUpParty in mUpPartys.OrderBy(p => p.Type).ThenBy(p => p.Name))
+ foreach (var mUpParty in mUpPartys.OrderBy(p => p.Type).ThenBy(p => p.Name))
{
aUpPartys.Add(mapper.Map(mUpParty));
}
@@ -67,5 +59,36 @@ await tenantDataRepository.GetListAsync>(idKe
throw;
}
}
+
+ private async Task<(IReadOnlyCollection> items, string paginationToken)> GetFilterUpPartyInternalAsync(string filterName, string filterHrdDomains)
+ {
+ var doFilterPartyType = Enum.TryParse(filterName, out var filterPartyType);
+ var idKey = new Track.IdKey { TenantName = RouteBinding.TenantName, TrackName = RouteBinding.TrackName };
+
+ if (filterName.IsNullOrWhiteSpace() && filterHrdDomains.IsNullOrWhiteSpace())
+ {
+ return await tenantDataRepository.GetListAsync>(idKey, whereQuery: p => p.DataType.Equals(dataType));
+ }
+ else if(!filterName.IsNullOrWhiteSpace() && filterHrdDomains.IsNullOrWhiteSpace())
+ {
+ return await tenantDataRepository.GetListAsync>(idKey, whereQuery: p => p.DataType.Equals(dataType) &&
+ (p.Name.Contains(filterName, StringComparison.CurrentCultureIgnoreCase) || p.DisplayName.Contains(filterName, StringComparison.CurrentCultureIgnoreCase) ||
+ (p.Profiles != null && p.Profiles.Any(p => p.Name.Contains(filterName, StringComparison.CurrentCultureIgnoreCase) || p.DisplayName.Contains(filterName, StringComparison.CurrentCultureIgnoreCase))) ||
+ (doFilterPartyType && p.Type == filterPartyType)));
+ }
+ else if (filterName.IsNullOrWhiteSpace() && !filterHrdDomains.IsNullOrWhiteSpace())
+ {
+ return await tenantDataRepository.GetListAsync>(idKey, whereQuery: p => p.DataType.Equals(dataType) &&
+ p.HrdDomains.Where(d => d.Contains(filterHrdDomains, StringComparison.CurrentCultureIgnoreCase)).Any());
+ }
+ else
+ {
+ return await tenantDataRepository.GetListAsync>(idKey, whereQuery: p => p.DataType.Equals(dataType) &&
+ (p.Name.Contains(filterName, StringComparison.CurrentCultureIgnoreCase) || p.DisplayName.Contains(filterName, StringComparison.CurrentCultureIgnoreCase) ||
+ (p.Profiles != null && p.Profiles.Any(p => p.Name.Contains(filterName, StringComparison.CurrentCultureIgnoreCase) || p.DisplayName.Contains(filterName, StringComparison.CurrentCultureIgnoreCase))) ||
+ (doFilterPartyType && p.Type == filterPartyType)) ||
+ p.HrdDomains.Where(d => d.Contains(filterHrdDomains, StringComparison.CurrentCultureIgnoreCase)).Any());
+ }
+ }
}
}
diff --git a/src/FoxIDs.Control/Controllers/Parties/TLoginUpPartyController.cs b/src/FoxIDs.Control/Controllers/Parties/TLoginUpPartyController.cs
index 4b876e627..c3073b205 100644
--- a/src/FoxIDs.Control/Controllers/Parties/TLoginUpPartyController.cs
+++ b/src/FoxIDs.Control/Controllers/Parties/TLoginUpPartyController.cs
@@ -8,6 +8,7 @@
using AutoMapper;
using FoxIDs.Logic;
using FoxIDs.Models.Config;
+using FoxIDs.Logic.Queues;
namespace FoxIDs.Controllers
{
diff --git a/src/FoxIDs.Control/Controllers/Parties/TOAuthDownPartyController.cs b/src/FoxIDs.Control/Controllers/Parties/TOAuthDownPartyController.cs
index 348a5194a..fcdf0be18 100644
--- a/src/FoxIDs.Control/Controllers/Parties/TOAuthDownPartyController.cs
+++ b/src/FoxIDs.Control/Controllers/Parties/TOAuthDownPartyController.cs
@@ -8,6 +8,7 @@
using AutoMapper;
using FoxIDs.Logic;
using FoxIDs.Models.Config;
+using FoxIDs.Logic.Queues;
namespace FoxIDs.Controllers
{
diff --git a/src/FoxIDs.Control/Controllers/Parties/TOAuthUpPartyController.cs b/src/FoxIDs.Control/Controllers/Parties/TOAuthUpPartyController.cs
index 8ec41ea5f..a4bdfa537 100644
--- a/src/FoxIDs.Control/Controllers/Parties/TOAuthUpPartyController.cs
+++ b/src/FoxIDs.Control/Controllers/Parties/TOAuthUpPartyController.cs
@@ -8,6 +8,7 @@
using AutoMapper;
using FoxIDs.Logic;
using FoxIDs.Models.Config;
+using FoxIDs.Logic.Queues;
namespace FoxIDs.Controllers
{
diff --git a/src/FoxIDs.Control/Controllers/Parties/TOidcDownPartyController.cs b/src/FoxIDs.Control/Controllers/Parties/TOidcDownPartyController.cs
index 7daf1c540..470ee289c 100644
--- a/src/FoxIDs.Control/Controllers/Parties/TOidcDownPartyController.cs
+++ b/src/FoxIDs.Control/Controllers/Parties/TOidcDownPartyController.cs
@@ -8,6 +8,7 @@
using AutoMapper;
using FoxIDs.Logic;
using FoxIDs.Models.Config;
+using FoxIDs.Logic.Queues;
namespace FoxIDs.Controllers
{
diff --git a/src/FoxIDs.Control/Controllers/Parties/TOidcUpPartyController.cs b/src/FoxIDs.Control/Controllers/Parties/TOidcUpPartyController.cs
index ecc20229e..364dca1ba 100644
--- a/src/FoxIDs.Control/Controllers/Parties/TOidcUpPartyController.cs
+++ b/src/FoxIDs.Control/Controllers/Parties/TOidcUpPartyController.cs
@@ -8,6 +8,7 @@
using AutoMapper;
using FoxIDs.Logic;
using FoxIDs.Models.Config;
+using FoxIDs.Logic.Queues;
namespace FoxIDs.Controllers
{
diff --git a/src/FoxIDs.Control/Controllers/Parties/TSamlDownPartyController.cs b/src/FoxIDs.Control/Controllers/Parties/TSamlDownPartyController.cs
index 21b61aba5..6dc7821e2 100644
--- a/src/FoxIDs.Control/Controllers/Parties/TSamlDownPartyController.cs
+++ b/src/FoxIDs.Control/Controllers/Parties/TSamlDownPartyController.cs
@@ -8,6 +8,7 @@
using AutoMapper;
using FoxIDs.Logic;
using FoxIDs.Models.Config;
+using FoxIDs.Logic.Queues;
namespace FoxIDs.Controllers
{
diff --git a/src/FoxIDs.Control/Controllers/Parties/TSamlUpPartyController.cs b/src/FoxIDs.Control/Controllers/Parties/TSamlUpPartyController.cs
index c2e8075c0..9bdc7c2f2 100644
--- a/src/FoxIDs.Control/Controllers/Parties/TSamlUpPartyController.cs
+++ b/src/FoxIDs.Control/Controllers/Parties/TSamlUpPartyController.cs
@@ -8,6 +8,7 @@
using AutoMapper;
using FoxIDs.Logic;
using FoxIDs.Models.Config;
+using FoxIDs.Logic.Queues;
namespace FoxIDs.Controllers
{
diff --git a/src/FoxIDs.Control/Controllers/Parties/TTrackLinkDownPartyController.cs b/src/FoxIDs.Control/Controllers/Parties/TTrackLinkDownPartyController.cs
index 78daeb04d..b17835b8e 100644
--- a/src/FoxIDs.Control/Controllers/Parties/TTrackLinkDownPartyController.cs
+++ b/src/FoxIDs.Control/Controllers/Parties/TTrackLinkDownPartyController.cs
@@ -8,6 +8,7 @@
using AutoMapper;
using FoxIDs.Logic;
using FoxIDs.Models.Config;
+using FoxIDs.Logic.Queues;
namespace FoxIDs.Controllers
{
diff --git a/src/FoxIDs.Control/Controllers/Parties/TTrackLinkUpPartyController.cs b/src/FoxIDs.Control/Controllers/Parties/TTrackLinkUpPartyController.cs
index f42d39db0..12faa919f 100644
--- a/src/FoxIDs.Control/Controllers/Parties/TTrackLinkUpPartyController.cs
+++ b/src/FoxIDs.Control/Controllers/Parties/TTrackLinkUpPartyController.cs
@@ -8,6 +8,7 @@
using AutoMapper;
using FoxIDs.Logic;
using FoxIDs.Models.Config;
+using FoxIDs.Logic.Queues;
namespace FoxIDs.Controllers
{
diff --git a/src/FoxIDs.Control/Controllers/Tenants/TFilterTenantController.cs b/src/FoxIDs.Control/Controllers/Tenants/TFilterTenantController.cs
index 5ac807c5a..3114866a0 100644
--- a/src/FoxIDs.Control/Controllers/Tenants/TFilterTenantController.cs
+++ b/src/FoxIDs.Control/Controllers/Tenants/TFilterTenantController.cs
@@ -33,7 +33,8 @@ public TFilterTenantController(TelemetryScopedLogger logger, IMapper mapper, ITe
///
/// Filter tenant.
///
- /// Filter tenant name.
+ /// Filter by tenant name.
+ /// Filter by custom domain.
/// Tenants.
[ProducesResponseType(typeof(HashSet), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
@@ -64,19 +65,19 @@ public TFilterTenantController(TelemetryScopedLogger logger, IMapper mapper, ITe
{
if (filterName.IsNullOrWhiteSpace() && filterCustomDomain.IsNullOrWhiteSpace())
{
- return tenantDataRepository.GetListAsync();
+ return tenantDataRepository.GetListAsync(whereQuery: t => !t.ForUsage && t.Name != Constants.Routes.MasterTenantName);
}
else if(!filterName.IsNullOrWhiteSpace() && filterCustomDomain.IsNullOrWhiteSpace())
{
- return tenantDataRepository.GetListAsync(whereQuery: t => t.Name.Contains(filterName, StringComparison.CurrentCultureIgnoreCase));
+ return tenantDataRepository.GetListAsync(whereQuery: t => !t.ForUsage && t.Name != Constants.Routes.MasterTenantName && t.Name.Contains(filterName, StringComparison.CurrentCultureIgnoreCase));
}
else if (filterName.IsNullOrWhiteSpace() && !filterCustomDomain.IsNullOrWhiteSpace())
{
- return tenantDataRepository.GetListAsync(whereQuery: t => t.CustomDomain.Contains(filterCustomDomain, StringComparison.CurrentCultureIgnoreCase));
+ return tenantDataRepository.GetListAsync(whereQuery: t => !t.ForUsage && t.Name != Constants.Routes.MasterTenantName && t.CustomDomain.Contains(filterCustomDomain, StringComparison.CurrentCultureIgnoreCase));
}
else
{
- return tenantDataRepository.GetListAsync(whereQuery: t => t.Name.Contains(filterName, StringComparison.CurrentCultureIgnoreCase) || t.CustomDomain.Contains(filterCustomDomain, StringComparison.CurrentCultureIgnoreCase));
+ return tenantDataRepository.GetListAsync(whereQuery: t => !t.ForUsage && t.Name != Constants.Routes.MasterTenantName && t.Name.Contains(filterName, StringComparison.CurrentCultureIgnoreCase) || t.CustomDomain.Contains(filterCustomDomain, StringComparison.CurrentCultureIgnoreCase));
}
}
}
diff --git a/src/FoxIDs.Control/Controllers/Tenants/TFilterUsageTenantController.cs b/src/FoxIDs.Control/Controllers/Tenants/TFilterUsageTenantController.cs
new file mode 100644
index 000000000..af18d540f
--- /dev/null
+++ b/src/FoxIDs.Control/Controllers/Tenants/TFilterUsageTenantController.cs
@@ -0,0 +1,66 @@
+using AutoMapper;
+using FoxIDs.Infrastructure;
+using FoxIDs.Repository;
+using FoxIDs.Models;
+using Api = FoxIDs.Models.Api;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using System.Threading.Tasks;
+using System.Collections.Generic;
+using System.Linq;
+using ITfoxtec.Identity;
+using FoxIDs.Infrastructure.Security;
+using FoxIDs.Infrastructure.Filters;
+using System;
+
+namespace FoxIDs.Controllers
+{
+ [RequireMasterTenant]
+ [MasterScopeAuthorize]
+ public class TFilterUsageTenantController : ApiController
+ {
+ private readonly TelemetryScopedLogger logger;
+ private readonly IMapper mapper;
+ private readonly ITenantDataRepository tenantDataRepository;
+
+ public TFilterUsageTenantController(TelemetryScopedLogger logger, IMapper mapper, ITenantDataRepository tenantDataRepository) : base(logger)
+ {
+ this.logger = logger;
+ this.mapper = mapper;
+ this.tenantDataRepository = tenantDataRepository;
+ }
+
+ ///
+ /// Filter usage tenant.
+ ///
+ /// Filter usage tenant name.
+ /// Tenants.
+ [ProducesResponseType(typeof(HashSet), StatusCodes.Status200OK)]
+ [ProducesResponseType(StatusCodes.Status404NotFound)]
+ public async Task>> GetFilterUsageTenant(string filterName)
+ {
+ try
+ {
+ (var mTenants, _) = filterName.IsNullOrWhiteSpace() ?
+ await tenantDataRepository.GetListAsync(whereQuery: t => t.ForUsage && t.Name != Constants.Routes.MasterTenantName) :
+ await tenantDataRepository.GetListAsync(whereQuery: t => t.ForUsage && t.Name != Constants.Routes.MasterTenantName && t.Name.Contains(filterName, StringComparison.CurrentCultureIgnoreCase));
+
+ var aTenants = new HashSet(mTenants.Count());
+ foreach (var mTenant in mTenants.OrderBy(t => t.Name))
+ {
+ aTenants.Add(mapper.Map(mTenant));
+ }
+ return Ok(aTenants);
+ }
+ catch (FoxIDsDataException ex)
+ {
+ if (ex.StatusCode == DataStatusCode.NotFound)
+ {
+ logger.Warning(ex, $"NotFound, Get '{typeof(Api.Tenant).Name}' by filter name '{filterName}'.");
+ return NotFound(typeof(Api.Tenant).Name, filterName);
+ }
+ throw;
+ }
+ }
+ }
+}
diff --git a/src/FoxIDs.Control/Controllers/Tenants/TMyMollieFirstPaymentController.cs b/src/FoxIDs.Control/Controllers/Tenants/TMyMollieFirstPaymentController.cs
new file mode 100644
index 000000000..fc013015a
--- /dev/null
+++ b/src/FoxIDs.Control/Controllers/Tenants/TMyMollieFirstPaymentController.cs
@@ -0,0 +1,107 @@
+using FoxIDs.Infrastructure;
+using FoxIDs.Repository;
+using FoxIDs.Models;
+using Api = FoxIDs.Models.Api;
+using Microsoft.AspNetCore.Http;
+using Microsoft.AspNetCore.Mvc;
+using System.Threading.Tasks;
+using System;
+using FoxIDs.Infrastructure.Security;
+using FoxIDs.Models.Config;
+using Mollie.Api.Client.Abstract;
+using Mollie.Api.Client;
+using Mollie.Api.Models;
+using Mollie.Api.Models.Payment;
+using Mollie.Api.Models.Customer.Request;
+using Mollie.Api.Models.Payment.Request.PaymentSpecificParameters;
+using ITfoxtec.Identity;
+using FoxIDs.Logic.Usage;
+
+namespace FoxIDs.Controllers
+{
+ ///
+ /// Register first Mollie payment.
+ ///
+ [TenantScopeAuthorize]
+ public class TMyMollieFirstPaymentController : ApiController
+ {
+ private readonly FoxIDsControlSettings settings;
+ private readonly TelemetryScopedLogger logger;
+ private readonly ITenantDataRepository tenantDataRepository;
+ private readonly ICustomerClient customerClient;
+ private readonly IPaymentClient paymentClient;
+ private readonly UsageMolliePaymentLogic usageMolliePaymentLogic;
+
+ public TMyMollieFirstPaymentController(FoxIDsControlSettings settings, TelemetryScopedLogger logger, ITenantDataRepository tenantDataRepository, ICustomerClient customerClient, IPaymentClient paymentClient, UsageMolliePaymentLogic usageMolliePaymentLogic) : base(logger)
+ {
+ this.settings = settings;
+ this.logger = logger;
+ this.tenantDataRepository = tenantDataRepository;
+ this.customerClient = customerClient;
+ this.paymentClient = paymentClient;
+ this.usageMolliePaymentLogic = usageMolliePaymentLogic;
+ }
+
+ ///
+ /// Register first Mollie payment.
+ ///
+ /// First payment.
+ /// Tenant.
+ [ProducesResponseType(typeof(Api.MollieFirstPaymentResponse), StatusCodes.Status201Created)]
+ [ProducesResponseType(StatusCodes.Status409Conflict)]
+ public async Task> PostMyMollieFirstPayment([FromBody] Api.MollieFirstPaymentRequest payment)
+ {
+ if(settings.Payment?.EnablePayment != true || settings.Usage?.EnableInvoice != true)
+ {
+ throw new Exception("Payment not configured.");
+ }
+
+ var mTenant = await tenantDataRepository.GetTenantByNameAsync(RouteBinding.TenantName);
+ if(string.IsNullOrWhiteSpace(mTenant.Payment?.CustomerId))
+ {
+ try
+ {
+ var customerResponse = await customerClient.CreateCustomerAsync(new CustomerRequest()
+ {
+ Name = RouteBinding.TenantName
+ });
+
+ mTenant.Payment = new Payment
+ {
+ CustomerId = customerResponse.Id
+ };
+ await tenantDataRepository.UpdateAsync(mTenant);
+ }
+ catch (MollieApiException ex)
+ {
+ logger.Error(ex, "Create Mollie customer error.");
+ throw new Exception("Unable to create customer in Mollie.");
+ }
+ }
+
+ var paymentRequest = new CreditCardPaymentRequest
+ {
+ Amount = new Amount("EUR", "0.00"),
+ RedirectUrl = $"{HttpContext.GetHost()}{RouteBinding.TenantName}/tenantpaymentresponse",
+ Description = "Zero amount registration payment",
+ CustomerId = mTenant.Payment.CustomerId,
+ SequenceType = SequenceType.First,
+ CardToken = payment.CardToken
+ };
+
+ var paymentResponse = await paymentClient.CreatePaymentAsync(paymentRequest);
+
+ mTenant.Payment.IsActive = false;
+ if (!mTenant.Payment.MandateId.IsNullOrWhiteSpace())
+ {
+ await usageMolliePaymentLogic.RevokePaymentMandateAsync(mTenant);
+ }
+ mTenant.Payment.MandateId = paymentResponse.MandateId;
+ await tenantDataRepository.UpdateAsync(mTenant);
+
+ await usageMolliePaymentLogic.UpdatePaymentMandate(mTenant);
+
+ return Ok(new Api.MollieFirstPaymentResponse { CheckoutUrl = paymentResponse.Links?.Checkout?.Href });
+ }
+ }
+}
diff --git a/src/FoxIDs.Control/Controllers/Tenants/TMyTenantController.cs b/src/FoxIDs.Control/Controllers/Tenants/TMyTenantController.cs
index 2d870e887..ed9b09746 100644
--- a/src/FoxIDs.Control/Controllers/Tenants/TMyTenantController.cs
+++ b/src/FoxIDs.Control/Controllers/Tenants/TMyTenantController.cs
@@ -12,27 +12,31 @@
using FoxIDs.Infrastructure.Security;
using Microsoft.Extensions.DependencyInjection;
using FoxIDs.Models.Config;
+using System.Linq;
+using FoxIDs.Logic.Usage;
namespace FoxIDs.Controllers
{
[TenantScopeAuthorize]
public class TMyTenantController : ApiController
{
- private readonly Settings settings;
+ private readonly FoxIDsControlSettings settings;
private readonly TelemetryScopedLogger logger;
private readonly IServiceProvider serviceProvider;
private readonly IMapper mapper;
+ private readonly IMasterDataRepository masterDataRepository;
private readonly ITenantDataRepository tenantDataRepository;
private readonly PlanCacheLogic planCacheLogic;
private readonly TenantCacheLogic tenantCacheLogic;
private readonly TrackCacheLogic trackCacheLogic;
- public TMyTenantController(Settings settings, TelemetryScopedLogger logger, IServiceProvider serviceProvider, IMapper mapper, ITenantDataRepository tenantDataRepository, PlanCacheLogic planCacheLogic, TenantCacheLogic tenantCacheLogic, TrackCacheLogic trackCacheLogic) : base(logger)
+ public TMyTenantController(FoxIDsControlSettings settings, TelemetryScopedLogger logger, IServiceProvider serviceProvider, IMapper mapper, IMasterDataRepository masterDataRepository, ITenantDataRepository tenantDataRepository, PlanCacheLogic planCacheLogic, TenantCacheLogic tenantCacheLogic, TrackCacheLogic trackCacheLogic) : base(logger)
{
this.settings = settings;
this.logger = logger;
this.serviceProvider = serviceProvider;
this.mapper = mapper;
+ this.masterDataRepository = masterDataRepository;
this.tenantDataRepository = tenantDataRepository;
this.planCacheLogic = planCacheLogic;
this.tenantCacheLogic = tenantCacheLogic;
@@ -43,14 +47,17 @@ public TMyTenantController(Settings settings, TelemetryScopedLogger logger, ISer
/// Get my tenant.
///
/// Tenant.
- [ProducesResponseType(typeof(Api.Tenant), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(Api.TenantResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task> GetMyTenant()
+ public async Task> GetMyTenant()
{
try
{
- var MTenant = await tenantDataRepository.GetTenantByNameAsync(RouteBinding.TenantName);
- return Ok(mapper.Map(MTenant));
+ var mTenant = await tenantDataRepository.GetTenantByNameAsync(RouteBinding.TenantName);
+
+ await UpdatePaymentMandate(mTenant);
+
+ return Ok(mapper.Map(mTenant));
}
catch (FoxIDsDataException ex)
{
@@ -68,9 +75,9 @@ public TMyTenantController(Settings settings, TelemetryScopedLogger logger, ISer
///
/// Tenant.
/// Tenant.
- [ProducesResponseType(typeof(Api.Tenant), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(Api.TenantResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task> PutMyTenant([FromBody] Api.MyTenantRequest tenant)
+ public async Task> PutMyTenant([FromBody] Api.MyTenantRequest tenant)
{
try
{
@@ -87,8 +94,31 @@ public TMyTenantController(Settings settings, TelemetryScopedLogger logger, ISer
}
}
+ try
+ {
+ if (settings.Payment.EnablePayment == true && settings.Usage?.EnableInvoice == true && !tenant.PlanName.IsNullOrEmpty())
+ {
+ tenant.PlanName = tenant.PlanName.ToLower();
+ if (tenant.PlanName != RouteBinding.PlanName)
+ {
+ var mPlans = await masterDataRepository.GetListAsync();
+ decimal currentCost = RouteBinding.PlanName.IsNullOrEmpty() ? 0 : mPlans.Where(p => p.Name == RouteBinding.PlanName).Select(p => p.CostPerMonth).FirstOrDefault();
+ decimal updateCost = mPlans.Where(p => p.Name == tenant.PlanName).Select(p => p.CostPerMonth).FirstOrDefault();
+ if (updateCost >= currentCost)
+ {
+ mTenant.PlanName = tenant.PlanName;
+ }
+ }
+ }
+ }
+ catch (Exception exp)
+ {
+ logger.Error(exp, "Unable to update plan in tenant.");
+ }
+
mTenant.CustomDomain = tenant.CustomDomain;
mTenant.CustomDomainVerified = false;
+ mTenant.Customer = mapper.Map(tenant.Customer);
await tenantDataRepository.UpdateAsync(mTenant);
await tenantCacheLogic.InvalidateTenantCacheAsync(RouteBinding.TenantName);
@@ -97,7 +127,9 @@ public TMyTenantController(Settings settings, TelemetryScopedLogger logger, ISer
await tenantCacheLogic.InvalidateCustomDomainCacheAsync(invalidateCustomDomainInCache);
}
- return Ok(mapper.Map(mTenant));
+ await UpdatePaymentMandate(mTenant);
+
+ return Ok(mapper.Map(mTenant));
}
catch (FoxIDsDataException ex)
{
@@ -110,6 +142,15 @@ public TMyTenantController(Settings settings, TelemetryScopedLogger logger, ISer
}
}
+ private async Task UpdatePaymentMandate(Tenant mTenant)
+ {
+ if (settings.Payment?.EnablePayment == true && settings.Usage?.EnableInvoice == true)
+ {
+ var usageMolliePaymentLogic = serviceProvider.GetService();
+ await usageMolliePaymentLogic.UpdatePaymentMandate(mTenant);
+ }
+ }
+
///
/// Delete my tenant.
///
diff --git a/src/FoxIDs.Control/Controllers/Tenants/TMyTenantLogUsageController.cs b/src/FoxIDs.Control/Controllers/Tenants/TMyTenantLogUsageController.cs
index 9f8ef1b00..fe8cce372 100644
--- a/src/FoxIDs.Control/Controllers/Tenants/TMyTenantLogUsageController.cs
+++ b/src/FoxIDs.Control/Controllers/Tenants/TMyTenantLogUsageController.cs
@@ -39,7 +39,7 @@ public TMyTenantLogUsageController(TelemetryScopedLogger logger, UsageLogLogic u
logRequest.TrackName = null;
}
- var logResponse = await usageLogLogic.GetTrackUsageLog(logRequest, RouteBinding.TenantName, logRequest.TrackName, isMasterTrack: true);
+ var logResponse = await usageLogLogic.GetTrackUsageLogAsync(logRequest, RouteBinding.TenantName, logRequest.TrackName, isMasterTrack: true);
return Ok(logResponse);
}
}
diff --git a/src/FoxIDs.Control/Controllers/Tenants/TTenantController.cs b/src/FoxIDs.Control/Controllers/Tenants/TTenantController.cs
index ddb997a2c..0a144333f 100644
--- a/src/FoxIDs.Control/Controllers/Tenants/TTenantController.cs
+++ b/src/FoxIDs.Control/Controllers/Tenants/TTenantController.cs
@@ -13,6 +13,7 @@
using ITfoxtec.Identity;
using FoxIDs.Models.Config;
using Microsoft.Extensions.DependencyInjection;
+using FoxIDs.Logic.Usage;
namespace FoxIDs.Controllers
{
@@ -48,17 +49,21 @@ public TTenantController(FoxIDsControlSettings settings, TelemetryScopedLogger l
///
/// Tenant name.
/// Tenant.
- [ProducesResponseType(typeof(Api.Tenant), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(Api.TenantResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task> GetTenant(string name)
+ public async Task> GetTenant(string name)
{
try
{
if (!ModelState.TryValidateRequiredParameter(name, nameof(name))) return BadRequest(ModelState);
name = name?.ToLower();
- var MTenant = await tenantDataRepository.GetTenantByNameAsync(name);
- return Ok(mapper.Map(MTenant));
+ var mTenant = await tenantDataRepository.GetTenantByNameAsync(name);
+ if (!mTenant.ForUsage)
+ {
+ await UpdatePaymentMandate(mTenant);
+ }
+ return Ok(mapper.Map(mTenant));
}
catch (FoxIDsDataException ex)
{
@@ -76,50 +81,63 @@ public TTenantController(FoxIDsControlSettings settings, TelemetryScopedLogger l
///
/// Tenant.
/// Tenant.
- [ProducesResponseType(typeof(Api.Tenant), StatusCodes.Status201Created)]
+ [ProducesResponseType(typeof(Api.TenantResponse), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
- public async Task> PostTenant([FromBody] Api.CreateTenantRequest tenant)
+ public async Task> PostTenant([FromBody] Api.CreateTenantRequest tenant)
{
try
{
if (!await ModelState.TryValidateObjectAsync(tenant)) return BadRequest(ModelState);
tenant.Name = tenant.Name.ToLower();
- tenant.AdministratorEmail = tenant.AdministratorEmail?.ToLower();
- if (tenant.Name == Constants.Routes.ControlSiteName || tenant.Name == Constants.Routes.HealthController)
+ if (!tenant.ForUsage)
{
- throw new FoxIDsDataException($"A tenant can not have the name '{tenant.Name}'.") { StatusCode = DataStatusCode.Conflict };
+ tenant.AdministratorEmail = tenant.AdministratorEmail?.ToLower();
+
+ if (tenant.Name == Constants.Routes.ControlSiteName || tenant.Name == Constants.Routes.HealthController)
+ {
+ throw new FoxIDsDataException($"A tenant can not have the name '{tenant.Name}'.") { StatusCode = DataStatusCode.Conflict };
+ }
}
- (var validPlan, var plan) = await ValidatePlanAsync(tenant.Name, nameof(tenant.PlanName), tenant.PlanName);
- if (!validPlan) return BadRequest(ModelState);
+ var mTenant = mapper.Map(tenant);
+ mTenant.Customer = mapper.Map(tenant.Customer);
+ mTenant.CreateTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
- if (plan != null && !tenant.CustomDomain.IsNullOrEmpty())
+ if (!tenant.ForUsage)
{
- if (!plan.EnableCustomDomain)
+ (var validPlan, var plan) = await ValidatePlanAsync(tenant.Name, nameof(tenant.PlanName), tenant.PlanName);
+ if (!validPlan) return BadRequest(ModelState);
+
+ if (plan != null && !tenant.CustomDomain.IsNullOrEmpty())
{
- throw new Exception($"Custom domain is not supported in the '{plan.Name}' plan.");
+ if (!plan.EnableCustomDomain)
+ {
+ throw new Exception($"Custom domain is not supported in the '{plan.Name}' plan.");
+ }
}
+ mTenant.PlanName = plan?.Name;
}
- var mTenant = mapper.Map(tenant);
await tenantDataRepository.CreateAsync(mTenant);
- await tenantCacheLogic.InvalidateTenantCacheAsync(tenant.Name);
- if (!string.IsNullOrEmpty(tenant.CustomDomain))
+ if (!tenant.ForUsage)
{
- await tenantCacheLogic.InvalidateCustomDomainCacheAsync(tenant.CustomDomain);
- }
-
- await masterTenantLogic.CreateMasterTrackDocumentAsync(tenant.Name);
- var mLoginUpParty = await masterTenantLogic.CreateMasterLoginDocumentAsync(tenant.Name);
- await masterTenantLogic.CreateFirstAdminUserDocumentAsync(tenant.Name, tenant.AdministratorEmail, tenant.AdministratorPassword, tenant.ChangeAdministratorPassword, true, tenant.ConfirmAdministratorAccount);
- await masterTenantLogic.CreateMasterFoxIDsControlApiResourceDocumentAsync(tenant.Name);
- await masterTenantLogic.CreateMasterControlClientDocmentAsync(tenant.Name, tenant.ControlClientBaseUri, mLoginUpParty);
+ await tenantCacheLogic.InvalidateTenantCacheAsync(tenant.Name);
+ if (!string.IsNullOrEmpty(tenant.CustomDomain))
+ {
+ await tenantCacheLogic.InvalidateCustomDomainCacheAsync(tenant.CustomDomain);
+ }
- await masterTenantLogic.CreateDefaultTracksDocmentsAsync(tenant.Name);
+ await masterTenantLogic.CreateMasterTrackDocumentAsync(tenant.Name);
+ var mLoginUpParty = await masterTenantLogic.CreateMasterLoginDocumentAsync(tenant.Name);
+ await masterTenantLogic.CreateFirstAdminUserDocumentAsync(tenant.Name, tenant.AdministratorEmail, tenant.AdministratorPassword, tenant.ChangeAdministratorPassword, true, tenant.ConfirmAdministratorAccount);
+ await masterTenantLogic.CreateMasterFoxIDsControlApiResourceDocumentAsync(tenant.Name);
+ await masterTenantLogic.CreateMasterControlClientDocmentAsync(tenant.Name, tenant.ControlClientBaseUri, mLoginUpParty);
- return Created(mapper.Map(mTenant));
+ await masterTenantLogic.CreateDefaultTracksDocmentsAsync(tenant.Name);
+ }
+ return Created(mapper.Map(mTenant));
}
catch (AccountException aex)
{
@@ -161,9 +179,9 @@ public TTenantController(FoxIDsControlSettings settings, TelemetryScopedLogger l
///
/// Tenant.
/// Tenant.
- [ProducesResponseType(typeof(Api.Tenant), StatusCodes.Status200OK)]
+ [ProducesResponseType(typeof(Api.TenantResponse), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
- public async Task> PutTenant([FromBody] Api.TenantRequest tenant)
+ public async Task> PutTenant([FromBody] Api.TenantRequest tenant)
{
try
{
@@ -171,32 +189,45 @@ public TTenantController(FoxIDsControlSettings settings, TelemetryScopedLogger l
tenant.Name = tenant.Name.ToLower();
var mTenant = await tenantDataRepository.GetTenantByNameAsync(tenant.Name);
-
var invalidateCustomDomainInCache = (!mTenant.CustomDomain.IsNullOrEmpty() && !mTenant.CustomDomain.Equals(tenant.CustomDomain, StringComparison.OrdinalIgnoreCase)) ? mTenant.CustomDomain : null;
- (var validPlan, var plan) = await ValidatePlanAsync(tenant.Name, nameof(tenant.PlanName), tenant.PlanName);
- if (!validPlan) return BadRequest(ModelState);
-
- mTenant.PlanName = tenant.PlanName;
-
- if (plan != null && !tenant.CustomDomain.IsNullOrEmpty())
+ if (!tenant.ForUsage)
{
- if (!plan.EnableCustomDomain)
+ (var validPlan, var plan) = await ValidatePlanAsync(tenant.Name, nameof(tenant.PlanName), tenant.PlanName);
+ if (!validPlan) return BadRequest(ModelState);
+
+ mTenant.PlanName = plan?.Name;
+
+ if (plan != null && !tenant.CustomDomain.IsNullOrEmpty())
{
- throw new Exception($"Custom domain is not supported in the '{plan.Name}' plan.");
+ if (!plan.EnableCustomDomain)
+ {
+ throw new Exception($"Custom domain is not supported in the '{plan.Name}' plan.");
+ }
}
+ mTenant.CustomDomain = tenant.CustomDomain;
+ mTenant.CustomDomainVerified = tenant.CustomDomainVerified;
}
- mTenant.CustomDomain = tenant.CustomDomain;
- mTenant.CustomDomainVerified = tenant.CustomDomainVerified;
+ mTenant.EnableUsage = tenant.EnableUsage;
+ mTenant.DoPayment = tenant.DoPayment;
+ mTenant.Currency = tenant.Currency;
+ mTenant.IncludeVat = tenant.IncludeVat;
+ mTenant.HourPrice = tenant.HourPrice;
+ mTenant.Customer = mapper.Map(tenant.Customer);
await tenantDataRepository.UpdateAsync(mTenant);
await tenantCacheLogic.InvalidateTenantCacheAsync(tenant.Name);
- if (!invalidateCustomDomainInCache.IsNullOrEmpty())
+ if (!tenant.ForUsage)
{
- await tenantCacheLogic.InvalidateCustomDomainCacheAsync(invalidateCustomDomainInCache);
+ if (!invalidateCustomDomainInCache.IsNullOrEmpty())
+ {
+ await tenantCacheLogic.InvalidateCustomDomainCacheAsync(invalidateCustomDomainInCache);
+ }
+
+ await UpdatePaymentMandate(mTenant);
}
- return Ok(mapper.Map(mTenant));
+ return Ok(mapper.Map(mTenant));
}
catch (FoxIDsDataException ex)
{
@@ -237,6 +268,15 @@ public TTenantController(FoxIDsControlSettings settings, TelemetryScopedLogger l
}
}
+ private async Task UpdatePaymentMandate(Tenant mTenant)
+ {
+ if (settings.Payment?.EnablePayment == true && settings.Usage?.EnableInvoice == true)
+ {
+ var usageMolliePaymentLogic = serviceProvider.GetService();
+ await usageMolliePaymentLogic.UpdatePaymentMandate(mTenant);
+ }
+ }
+
///
/// Delete tenant.
///
diff --git a/src/FoxIDs.Control/Controllers/Tenants/TTenantLogUsageController.cs b/src/FoxIDs.Control/Controllers/Tenants/TTenantLogUsageController.cs
index c8793ce96..b6c8d8f9b 100644
--- a/src/FoxIDs.Control/Controllers/Tenants/TTenantLogUsageController.cs
+++ b/src/FoxIDs.Control/Controllers/Tenants/TTenantLogUsageController.cs
@@ -50,7 +50,7 @@ public TTenantLogUsageController(TelemetryScopedLogger logger, UsageLogLogic usa
logRequest.TrackName = null;
}
- var logResponse = await usageLogLogic.GetTrackUsageLog(logRequest, logRequest.TenantName, logRequest.TrackName, isMasterTenant: true);
+ var logResponse = await usageLogLogic.GetTrackUsageLogAsync(logRequest, logRequest.TenantName, logRequest.TrackName, isMasterTenant: true);
return Ok(logResponse);
}
}
diff --git a/src/FoxIDs.Control/Controllers/Tracks/TExternalUserController.cs b/src/FoxIDs.Control/Controllers/Tracks/TExternalUserController.cs
index 72508ebde..086d6f91a 100644
--- a/src/FoxIDs.Control/Controllers/Tracks/TExternalUserController.cs
+++ b/src/FoxIDs.Control/Controllers/Tracks/TExternalUserController.cs
@@ -6,8 +6,6 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
-using System.Net;
-using ITfoxtec.Identity;
using FoxIDs.Infrastructure.Security;
using System;
using FoxIDs.Logic;
diff --git a/src/FoxIDs.Control/Controllers/Tracks/TFilterTrackController.cs b/src/FoxIDs.Control/Controllers/Tracks/TFilterTrackController.cs
index fc8fd1bd6..a36814f6e 100644
--- a/src/FoxIDs.Control/Controllers/Tracks/TFilterTrackController.cs
+++ b/src/FoxIDs.Control/Controllers/Tracks/TFilterTrackController.cs
@@ -6,7 +6,6 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
-using System.Net;
using System.Collections.Generic;
using System.Linq;
using ITfoxtec.Identity;
diff --git a/src/FoxIDs.Control/Controllers/Tracks/TTrackController.cs b/src/FoxIDs.Control/Controllers/Tracks/TTrackController.cs
index 6e96db0b7..41714d783 100644
--- a/src/FoxIDs.Control/Controllers/Tracks/TTrackController.cs
+++ b/src/FoxIDs.Control/Controllers/Tracks/TTrackController.cs
@@ -90,13 +90,13 @@ public TTrackController(FoxIDsControlSettings settings, TelemetryScopedLogger lo
if (!RouteBinding.PlanName.IsNullOrEmpty())
{
var plan = await planCacheLogic.GetPlanAsync(RouteBinding.PlanName);
- if (plan.Tracks.IsLimited)
+ if (plan.Tracks.LimitedThreshold > 0)
{
var count = await tenantDataRepository.CountAsync