public async Task <HSSupplier> UpdateSupplier(string supplierID, PartialSupplier supplier, DecodedToken decodedToken) { var me = await _oc.Me.GetAsync(accessToken : decodedToken.AccessToken); Require.That(decodedToken.CommerceRole == CommerceRole.Seller || supplierID == me.Supplier.ID, new ErrorCode("Unauthorized", $"You are not authorized to update supplier {supplierID}", 401)); var currentSupplier = await _oc.Suppliers.GetAsync <HSSupplier>(supplierID); var updatedSupplier = await _oc.Suppliers.PatchAsync <HSSupplier>(supplierID, supplier); // Update supplier products only on a name change if (currentSupplier.Name != supplier.Name || currentSupplier.xp.Currency.ToString() != supplier.xp.Currency.Value) { var productsToUpdate = await _oc.Products.ListAllAsync <HSProduct>( supplierID : supplierID, accessToken : decodedToken.AccessToken ); ApiClient supplierClient = await _apiClientHelper.GetSupplierApiClient(supplierID, decodedToken.AccessToken); if (supplierClient == null) { throw new Exception($"Default supplier client not found. SupplierID: {supplierID}"); } var configToUse = new OrderCloudClientConfig { ApiUrl = decodedToken.ApiUrl, AuthUrl = decodedToken.AuthUrl, ClientId = supplierClient.ID, ClientSecret = supplierClient.ClientSecret, GrantType = GrantType.ClientCredentials, Roles = new[] { ApiRole.SupplierAdmin, ApiRole.ProductAdmin }, }; var ocClient = new OrderCloudClient(configToUse); await ocClient.AuthenticateAsync(); var token = ocClient.TokenResponse.AccessToken; foreach (var product in productsToUpdate) { product.xp.Facets["supplier"] = new List <string>() { supplier.Name }; product.xp.Currency = supplier.xp.Currency; } await Throttler.RunAsync(productsToUpdate, 100, 5, product => ocClient.Products.SaveAsync(product.ID, product, accessToken: token)); } return(updatedSupplier); }
public async Task can_provide_alternative_token() { // prove that auth credentials are no longer required like in earlier versions var cli = new OrderCloudClient(new OrderCloudClientConfig { ApiUrl = "https://fake.com", AuthUrl = "https://fake.com" }); using (var httpTest = new HttpTest()) { var products = await cli.Me.ListProductsAsync(accessToken : "some-other-token"); httpTest.ShouldHaveMadeACall().WithHeader("Authorization", "Bearer some-other-token"); } }
public async Task <Product> FilterOptionOverride(string id, string supplierID, IDictionary <string, object> facets, VerifiedUserContext user) { ApiClient supplierClient = await _apiClientHelper.GetSupplierApiClient(supplierID, user.AccessToken); if (supplierClient == null) { throw new Exception($"Default supplier client not found. SupplierID: {supplierID}"); } var configToUse = new OrderCloudClientConfig { ApiUrl = user.TokenApiUrl, AuthUrl = user.TokenAuthUrl, ClientId = supplierClient.ID, ClientSecret = supplierClient.ClientSecret, GrantType = GrantType.ClientCredentials, Roles = new[] { ApiRole.SupplierAdmin, ApiRole.ProductAdmin }, }; var ocClient = new OrderCloudClient(configToUse); await ocClient.AuthenticateAsync(); var token = ocClient.TokenResponse.AccessToken; //Format the facet data to change for request body var facetDataFormatted = new ExpandoObject(); var facetDataFormattedCollection = (ICollection <KeyValuePair <string, object> >)facetDataFormatted; foreach (var kvp in facets) { facetDataFormattedCollection.Add(kvp); } dynamic facetDataFormattedDynamic = facetDataFormatted; //Update the product with a supplier token var updatedProduct = await ocClient.Products.PatchAsync( id, new PartialProduct() { xp = new { Facets = facetDataFormattedDynamic } }, accessToken : token ); return(updatedProduct); }
private async Task <VerifiedUserContext> ParseJwt(string token, ApiRole[] roles, IOrderCloudClient oc = null) { var jwt = new JwtSecurityToken(token); var clientId = jwt.Claims.FirstOrDefault(x => x.Type == "cid")?.Value; var usrtype = jwt.Claims.FirstOrDefault(x => x.Type == "usrtype")?.Value; var scope = jwt.Claims.Where(x => x.Type == "role").Select(x => x.Value)?.ToList(); var usr = jwt.Claims.FirstOrDefault(x => x.Type == "usr")?.Value; // validate scope if (!scope.Contains("FullAccess")) { Require.That(scope.Count(s => roles.Any(role => s == role.ToString())) > 0, new ErrorCode("Authorization.InvalidToken", 401, "Authorization.InvalidToken: Access token is invalid or expired.")); } var cid = new ClaimsIdentity("OrderCloudIntegrations"); cid.AddClaim(new Claim("clientid", clientId)); cid.AddClaim(new Claim("accesstoken", token)); if (oc == null) { oc = new OrderCloudClient(new OrderCloudClientConfig() { AuthUrl = _settings.OrderCloudSettings.ApiUrl, ApiUrl = _settings.OrderCloudSettings.ApiUrl, ClientId = clientId }); } var user = await oc.Me.GetAsync(token); if (!user.Active || user.Username != usr) { throw new Exception("Invalid User"); } cid.AddClaim(new Claim("userrecordjson", JsonConvert.SerializeObject(user))); cid.AddClaim(new Claim("username", user.Username)); cid.AddClaim(new Claim("access", user.Username)); cid.AddClaim(new Claim("userid", user.ID)); cid.AddClaim(new Claim("email", user.Email ?? "")); cid.AddClaim(new Claim("buyer", user.Buyer?.ID ?? "")); cid.AddClaim(new Claim("supplier", user.Supplier?.ID ?? "")); cid.AddClaim(new Claim("seller", user?.Seller?.ID ?? "")); cid.AddClaims(user.AvailableRoles.Select(r => new Claim(ClaimTypes.Role, r))); //Validate the token return(new VerifiedUserContext(new ClaimsPrincipal(cid))); }
// This method gets called by the runtime. Use this method to add services to the container. public override void Configure(IFunctionsHostBuilder builder) { var connectionString = Environment.GetEnvironmentVariable("APP_CONFIG_CONNECTION"); var _settings = builder.BuildSettingsFromAzureAppConfig <AppSettings>(connectionString); var orderCloudClient = new OrderCloudClient(new OrderCloudClientConfig { ApiUrl = _settings.OrderCloudSettings.ApiUrl, AuthUrl = _settings.OrderCloudSettings.ApiUrl, ClientId = _settings.OrderCloudSettings.MiddlewareClientID, ClientSecret = _settings.OrderCloudSettings.MiddlewareClientSecret, Roles = new[] { ApiRole.FullAccess } }); builder.Services.AddSingleton(_settings); builder.Services.AddSingleton <IOrderCloudClient>(orderCloudClient); }
// This method gets called by the runtime. Use this method to add services to the container. public override void Configure(IFunctionsHostBuilder builder) { var azureConnectionString = Environment.GetEnvironmentVariable("APP_CONFIG_CONNECTION"); var settings = BuildSettings(azureConnectionString); var orderCloudClient = new OrderCloudClient(new OrderCloudClientConfig { ApiUrl = settings.OrderCloudSettings.ApiUrl, AuthUrl = settings.OrderCloudSettings.ApiUrl, ClientId = settings.OrderCloudSettings.MiddlewareClientID, ClientSecret = settings.OrderCloudSettings.MiddlewareClientSecret, Roles = new[] { ApiRole.FullAccess } }); builder.Services.AddSingleton(settings); builder.Services.AddSingleton <IOrderCloudClient>(orderCloudClient); builder.Services.AddSingleton <IProductCommand, ProductCommand>(); builder.Services.AddSingleton <ForwardOrderJob>(); }
// This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { var cosmosConfig = new CosmosConfig( _settings.CosmosSettings.DatabaseName, _settings.CosmosSettings.EndpointUri, _settings.CosmosSettings.PrimaryKey, _settings.CosmosSettings.RequestTimeoutInSeconds ); var cosmosContainers = new List <ContainerInfo>() { new ContainerInfo() { Name = "salesorderdetail", PartitionKey = "/PartitionKey" }, new ContainerInfo() { Name = "purchaseorderdetail", PartitionKey = "/PartitionKey" }, new ContainerInfo() { Name = "lineitemdetail", PartitionKey = "/PartitionKey" }, new ContainerInfo() { Name = "rmas", PartitionKey = "/PartitionKey" }, new ContainerInfo() { Name = "shipmentdetail", PartitionKey = "/PartitionKey" }, new ContainerInfo() { Name = "productdetail", PartitionKey = "/PartitionKey" } }; var avalaraConfig = new AvalaraConfig() { BaseApiUrl = _settings.AvalaraSettings.BaseApiUrl, AccountID = _settings.AvalaraSettings.AccountID, LicenseKey = _settings.AvalaraSettings.LicenseKey, CompanyCode = _settings.AvalaraSettings.CompanyCode, CompanyID = _settings.AvalaraSettings.CompanyID }; var currencyConfig = new BlobServiceConfig() { ConnectionString = _settings.StorageAccountSettings.ConnectionString, Container = _settings.StorageAccountSettings.BlobContainerNameExchangeRates }; var assetConfig = new BlobServiceConfig() { ConnectionString = _settings.StorageAccountSettings.ConnectionString, Container = "assets", AccessType = BlobContainerPublicAccessType.Container }; var flurlClientFactory = new PerBaseUrlFlurlClientFactory(); var smartyStreetsUsClient = new ClientBuilder(_settings.SmartyStreetSettings.AuthID, _settings.SmartyStreetSettings.AuthToken).BuildUsStreetApiClient(); var orderCloudClient = new OrderCloudClient(new OrderCloudClientConfig { ApiUrl = _settings.OrderCloudSettings.ApiUrl, AuthUrl = _settings.OrderCloudSettings.ApiUrl, ClientId = _settings.OrderCloudSettings.MiddlewareClientID, ClientSecret = _settings.OrderCloudSettings.MiddlewareClientSecret, Roles = new[] { ApiRole.FullAccess } }); services.AddMvc(o => { o.Filters.Add(new ordercloud.integrations.library.ValidateModelAttribute()); o.EnableEndpointRouting = false; }) .ConfigureApiBehaviorOptions(o => o.SuppressModelStateInvalidFilter = true) .SetCompatibilityVersion(CompatibilityVersion.Version_3_0) .AddNewtonsoftJson(options => { options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver(); options.SerializerSettings.Converters.Add(new StringEnumConverter()); }); services .AddCors(o => o.AddPolicy("integrationcors", builder => { builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); })) .AddSingleton <ISimpleCache, LazyCacheService>() // Replace LazyCacheService with RedisService if you have multiple server instances. .AddOrderCloudUserAuth() .AddOrderCloudWebhookAuth(opts => opts.HashKey = _settings.OrderCloudSettings.WebhookHashKey) .InjectCosmosStore <LogQuery, OrchestrationLog>(cosmosConfig) .InjectCosmosStore <ReportTemplateQuery, ReportTemplate>(cosmosConfig) .AddCosmosDb(_settings.CosmosSettings.EndpointUri, _settings.CosmosSettings.PrimaryKey, _settings.CosmosSettings.DatabaseName, cosmosContainers) .Inject <IPortalService>() .Inject <ISmartyStreetsCommand>() .Inject <ICheckoutIntegrationCommand>() .Inject <IShipmentCommand>() .Inject <IOrderCommand>() .Inject <IPaymentCommand>() .Inject <IOrderSubmitCommand>() .Inject <IEnvironmentSeedCommand>() .Inject <IHSProductCommand>() .Inject <ILineItemCommand>() .Inject <IMeProductCommand>() .Inject <IHSCatalogCommand>() .Inject <ISendgridService>() .Inject <IHSSupplierCommand>() .Inject <ICreditCardCommand>() .Inject <ISupportAlertService>() .Inject <ISupplierApiClientHelper>() .AddSingleton <ISendGridClient>(x => new SendGridClient(_settings.SendgridSettings.ApiKey)) .AddSingleton <IFlurlClientFactory>(x => flurlClientFactory) .AddSingleton <DownloadReportCommand>() .Inject <IRMARepo>() .Inject <IZohoClient>() .AddSingleton <IZohoCommand>(z => new ZohoCommand(new ZohoClient( new ZohoClientConfig() { ApiUrl = "https://books.zoho.com/api/v3", AccessToken = _settings.ZohoSettings.AccessToken, ClientId = _settings.ZohoSettings.ClientId, ClientSecret = _settings.ZohoSettings.ClientSecret, OrganizationID = _settings.ZohoSettings.OrgID }, flurlClientFactory), orderCloudClient)) .AddSingleton <IOrderCloudIntegrationsExchangeRatesClient, OrderCloudIntegrationsExchangeRatesClient>() .AddSingleton <IAssetClient>(provider => new AssetClient(new OrderCloudIntegrationsBlobService(assetConfig), _settings)) .AddSingleton <IExchangeRatesCommand>(provider => new ExchangeRatesCommand(new OrderCloudIntegrationsBlobService(currencyConfig), flurlClientFactory, provider.GetService <ISimpleCache>())) .AddSingleton <IExchangeRatesCommand>(provider => new ExchangeRatesCommand(new OrderCloudIntegrationsBlobService(currencyConfig), flurlClientFactory, provider.GetService <ISimpleCache>())) .AddSingleton <IAvalaraCommand>(x => new AvalaraCommand( orderCloudClient, avalaraConfig, new AvaTaxClient("four51_headstart", "v1", "four51_headstart", new Uri(avalaraConfig.BaseApiUrl) ).WithSecurity(_settings.AvalaraSettings.AccountID, _settings.AvalaraSettings.LicenseKey), _settings.EnvironmentSettings.Environment.ToString())) .AddSingleton <IEasyPostShippingService>(x => new EasyPostShippingService(new EasyPostConfig() { APIKey = _settings.EasyPostSettings.APIKey })) .AddSingleton <ISmartyStreetsService>(x => new SmartyStreetsService(_settings.SmartyStreetSettings, smartyStreetsUsClient)) .AddSingleton <IOrderCloudIntegrationsCardConnectService>(x => new OrderCloudIntegrationsCardConnectService(_settings.CardConnectSettings, _settings.EnvironmentSettings.Environment.ToString(), flurlClientFactory)) .AddSingleton <IOrderCloudClient>(provider => orderCloudClient) .AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "Headstart Middleware API Documentation", Version = "v1" }); c.SchemaFilter <SwaggerExcludeFilter>(); }); var serviceProvider = services.BuildServiceProvider(); services .AddApplicationInsightsTelemetry(new ApplicationInsightsServiceOptions { EnableAdaptiveSampling = false, // retain all data InstrumentationKey = _settings.ApplicationInsightsSettings.InstrumentationKey }); ServicePointManager.DefaultConnectionLimit = int.MaxValue; FlurlHttp.Configure(settings => settings.Timeout = TimeSpan.FromSeconds(_settings.FlurlSettings.TimeoutInSeconds == 0 ? 30 : _settings.FlurlSettings.TimeoutInSeconds)); // This adds retry logic for any api call that fails with a transient error (server errors, timeouts, or rate limiting requests) // Will retry up to 3 times using exponential backoff and jitter, a mean of 3 seconds wait time in between retries // https://github.com/App-vNext/Polly/wiki/Retry-with-jitter#more-complex-jitter var delay = Backoff.DecorrelatedJitterBackoffV2(medianFirstRetryDelay: TimeSpan.FromSeconds(3), retryCount: 3); var policy = HttpPolicyExtensions .HandleTransientHttpError() .OrResult(response => response.StatusCode == HttpStatusCode.TooManyRequests) .WaitAndRetryAsync(delay); FlurlHttp.Configure(settings => settings.HttpClientFactory = new PollyFactory(policy)); }
// This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { var cosmosConfig = new CosmosConfig( _settings.CosmosSettings.DatabaseName, _settings.CosmosSettings.EndpointUri, _settings.CosmosSettings.PrimaryKey, _settings.CosmosSettings.RequestTimeoutInSeconds ); var cosmosContainers = new List <ContainerInfo>() { new ContainerInfo() { Name = "salesorderdetail", PartitionKey = "/PartitionKey" }, new ContainerInfo() { Name = "purchaseorderdetail", PartitionKey = "/PartitionKey" }, new ContainerInfo() { Name = "lineitemdetail", PartitionKey = "/PartitionKey" }, new ContainerInfo() { Name = "rmas", PartitionKey = "/PartitionKey" }, new ContainerInfo() { Name = "shipmentdetail", PartitionKey = "/PartitionKey" }, new ContainerInfo() { Name = "productdetail", PartitionKey = "/PartitionKey" } }; var avalaraConfig = new AvalaraConfig() { BaseApiUrl = _settings.AvalaraSettings.BaseApiUrl, AccountID = _settings.AvalaraSettings.AccountID, LicenseKey = _settings.AvalaraSettings.LicenseKey, CompanyCode = _settings.AvalaraSettings.CompanyCode, CompanyID = _settings.AvalaraSettings.CompanyID }; var currencyConfig = new BlobServiceConfig() { ConnectionString = _settings.StorageAccountSettings.ConnectionString, Container = _settings.StorageAccountSettings.BlobContainerNameExchangeRates }; var assetConfig = new BlobServiceConfig() { ConnectionString = _settings.StorageAccountSettings.ConnectionString, Container = "assets", AccessType = BlobContainerPublicAccessType.Container }; var flurlClientFactory = new PerBaseUrlFlurlClientFactory(); var smartyStreetsUsClient = new ClientBuilder(_settings.SmartyStreetSettings.AuthID, _settings.SmartyStreetSettings.AuthToken).BuildUsStreetApiClient(); var orderCloudClient = new OrderCloudClient(new OrderCloudClientConfig { ApiUrl = _settings.OrderCloudSettings.ApiUrl, AuthUrl = _settings.OrderCloudSettings.ApiUrl, ClientId = _settings.OrderCloudSettings.MiddlewareClientID, ClientSecret = _settings.OrderCloudSettings.MiddlewareClientSecret, Roles = new[] { ApiRole.FullAccess } }); AvalaraCommand avalaraCommand = null; VertexCommand vertexCommand = null; TaxJarCommand taxJarCommand = null; switch (_settings.EnvironmentSettings.TaxProvider) { case TaxProvider.Avalara: avalaraCommand = new AvalaraCommand(avalaraConfig, _settings.EnvironmentSettings.Environment.ToString()); break; case TaxProvider.Taxjar: taxJarCommand = new TaxJarCommand(_settings.TaxJarSettings); break; case TaxProvider.Vertex: vertexCommand = new VertexCommand(_settings.VertexSettings); break; default: break; } services.AddMvc(o => { o.Filters.Add(new ordercloud.integrations.library.ValidateModelAttribute()); o.EnableEndpointRouting = false; }) .ConfigureApiBehaviorOptions(o => o.SuppressModelStateInvalidFilter = true) .SetCompatibilityVersion(CompatibilityVersion.Version_3_0) .AddNewtonsoftJson(options => { options.SerializerSettings.ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver(); options.SerializerSettings.Converters.Add(new StringEnumConverter()); }); services .AddCors(o => o.AddPolicy("integrationcors", builder => { builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader(); })) .AddSingleton <ISimpleCache, LazyCacheService>() // Replace LazyCacheService with RedisService if you have multiple server instances. .AddOrderCloudUserAuth() .AddOrderCloudWebhookAuth(opts => opts.HashKey = _settings.OrderCloudSettings.WebhookHashKey) .InjectCosmosStore <LogQuery, OrchestrationLog>(cosmosConfig) .InjectCosmosStore <ReportTemplateQuery, ReportTemplate>(cosmosConfig) .AddCosmosDb(_settings.CosmosSettings.EndpointUri, _settings.CosmosSettings.PrimaryKey, _settings.CosmosSettings.DatabaseName, cosmosContainers) .Inject <IPortalService>() .Inject <ISmartyStreetsCommand>() .Inject <ICheckoutIntegrationCommand>() .Inject <IShipmentCommand>() .Inject <IOrderCommand>() .Inject <IPaymentCommand>() .Inject <IOrderSubmitCommand>() .Inject <IEnvironmentSeedCommand>() .Inject <IHSProductCommand>() .Inject <ILineItemCommand>() .Inject <IMeProductCommand>() .Inject <IDiscountDistributionService>() .Inject <IHSCatalogCommand>() .Inject <ISendgridService>() .Inject <IHSSupplierCommand>() .Inject <ICreditCardCommand>() .Inject <ISupportAlertService>() .Inject <ISupplierApiClientHelper>() .AddSingleton <ISendGridClient>(x => new SendGridClient(_settings.SendgridSettings.ApiKey)) .AddSingleton <IFlurlClientFactory>(x => flurlClientFactory) .AddSingleton <DownloadReportCommand>() .Inject <IRMARepo>() .Inject <IZohoClient>() .AddSingleton <IZohoCommand>(z => new ZohoCommand(new ZohoClient( new ZohoClientConfig() { ApiUrl = "https://books.zoho.com/api/v3", AccessToken = _settings.ZohoSettings.AccessToken, ClientId = _settings.ZohoSettings.ClientId, ClientSecret = _settings.ZohoSettings.ClientSecret, OrganizationID = _settings.ZohoSettings.OrgID }, flurlClientFactory), orderCloudClient)) .AddSingleton <IOrderCloudIntegrationsExchangeRatesClient, OrderCloudIntegrationsExchangeRatesClient>() .AddSingleton <IAssetClient>(provider => new AssetClient(new OrderCloudIntegrationsBlobService(assetConfig), _settings)) .AddSingleton <IExchangeRatesCommand>(provider => new ExchangeRatesCommand(new OrderCloudIntegrationsBlobService(currencyConfig), flurlClientFactory, provider.GetService <ISimpleCache>())) .AddSingleton <ITaxCodesProvider>(provider => { return(_settings.EnvironmentSettings.TaxProvider switch { TaxProvider.Avalara => avalaraCommand, TaxProvider.Taxjar => taxJarCommand, TaxProvider.Vertex => new NotImplementedTaxCodesProvider(), _ => avalaraCommand // Avalara is default }); })