unable to post to AXL endpoint - c#

My goal is to be able to post and retrieve from the endpoint which uses a SOAP based API
structure of my project
I generated a client with the WSDL file to target cucm 11.5, then
I followed the example on github by creating all the classes and interfaces as done on the repo
thirdly, my solution consist of two project a class library and a console project, the class library contains the generated client from the WSDL file and the console project consist of the class and interfaces to interact with the class library project
I have the following class to perform an operation
public class TestAxl
{
public void CreateUsers()
{
var axlClient = new AxlClient(new AxlClientConfiguration
{
Server = "Ip to the publish server",
User = "administrator",
Password = "password provided"
});
var addUserResult = axlClient.ExecuteAsync(async client =>
{
var userId = Guid.NewGuid().ToString();
var request = new AddUserReq
{
user = new XUser
{
userid = userId,
userIdentity = userId,
password = "P#ssw0rd",
firstName = "test",
lastName = "test"
}
};
var response = await client.addUserAsync(request);
return response.addUserResponse1.#return;
});
}
}
and i call it from the main class like so
class Program
{
static void Main(string[] args)
{
var letsDoSomeTesting = new TestAxl();
try
{
letsDoSomeTesting.CreateUsers();
}
catch (Exception e)
{
Console.WriteLine("The following is the exceeption from calling final class ", e.Message);
}
}
}
when i try to run the console project it starts and exit with 0,
then i go back to CUCM sandbox environment and nothing has changed, what could be the possible cause of this operation not working
FYI: Runtime netCore 3.1

I was able to get a sample project together including AXL/addUser, with DotNet Core 3.1 on Linux: https://github.com/CiscoDevNet/axl-dotnet-samples
This is the main section:
// Create a custom binding so we can allow the client to use cookies with AXL
BasicHttpsBinding binding = new BasicHttpsBinding();
binding.AllowCookies = true;
// Specify the CUCM AXL API location for the SOAP client
EndpointAddress address = new EndpointAddress( $"https://{ System.Environment.GetEnvironmentVariable( "CUCM_ADDRESS" ) }:8443/axl/" );
//Class generated from AXL WSDL
AXLPortClient client = new AXLPortClient( binding, address );
// To disable HTTPS certificate checking, uncomment the below lines
// NOT for production use!
// client.ChannelFactory.Credentials.ServiceCertificate.SslCertificateAuthentication = new X509ServiceCertificateAuthentication
// {
// CertificateValidationMode = X509CertificateValidationMode.None,
// RevocationMode = X509RevocationMode.NoCheck
// };
// client.ChannelFactory.Credentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
// client.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.None;
// Incantation to force alternate serializer reflection behaviour due to complexities in the AXL schema
// See https://github.com/dotnet/wcf/issues/2219
MethodInfo method = typeof( XmlSerializer ).GetMethod( "set_Mode", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static );
method.Invoke( null, new object[] { 1 } );
// Base64 encode AXL username/password for Basic Auth
var encodedUserPass = Convert.ToBase64String( Encoding.ASCII.GetBytes(
System.Environment.GetEnvironmentVariable( "CUCM_USERNAME" ) + ":" +
System.Environment.GetEnvironmentVariable( "CUCM_PASSWORD" )
) );
// Incantation to create and populate a Basic Auth HTTP header
// This must be done to force SoapCore to include the Authorization header on the first attempt
// rather than in challenge/response fashion
HttpRequestMessageProperty requestProperty = new HttpRequestMessageProperty();
requestProperty.Headers[ "Authorization" ] = "Basic " + encodedUserPass;
// Creating context block apparently allows attaching custom HTTP headers to the request
var scope = new OperationContextScope( client.InnerChannel );
OperationContext.Current.OutgoingMessageProperties[ HttpRequestMessageProperty.Name ] = requestProperty;
//Create the request object
AddUserReq addUserReq = new AddUserReq();
addUserReq.user = new XUser();
addUserReq.user.lastName = "TestUser";
addUserReq.user.userid = "testUser";
addUserReq.user.password = "Cisco!1234";
string userPkid = "";
//Try the addUser request
try
{
addUserResponse addUserResp = await client.addUserAsync( addUserReq );
userPkid = addUserResp.addUserResponse1.#return;
}
catch ( Exception ex )
{
Console.WriteLine( $"\nError: addUser: { ex.Message }" );
Environment.Exit( -1 );
}
A few notes:
SoapCore generates elements with default values when it can, e.g. nil for string elements. This causes a problem with <addUser>, as the <customerName> element should only be sent to HCS CUCMs. A modification to the AXLSoap.xsd before running svcutil was able to workaround it:
sed -i 's/name=\"customerName\" nillable=\"true\"/name=\"customerName\" nillable=\"false\"/g' schema/AXLSoap.xsd
Requests will fail due to HTTPS certification validation of the CUCM self-signed certificate, unless it is installed to the OS CA trust store or disabled (see the commented section in the code above)
The following curious code was required to avoid a "Compiling JScript/CSharp scripts is not supported" error on making a request (per here):
MethodInfo method = typeof( XmlSerializer ).GetMethod( "set_Mode", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static );
method.Invoke( null, new object[] { 1 } );

Related

Create Lambda proxy integration inside a non-root resource in API Gateway using AWS SDK with C#

The bounty expires in 7 days. Answers to this question are eligible for a +100 reputation bounty.
Harun Ćerim is looking for an answer from a reputable source:
A detailed explanation of how Lambda integration can be done on a non-root resource inside an API Gateway where routes are currently mapped to an underlying Lambda function and its underlying routes
I am trying to create a proxy integration for Lambda invocation from API Gateway for a specific route (example.com/auth/{proxy+}, example.com/user/{proxy+}, etc..).
I am creating a script for automated infrastructure initialization and deployment (without using CloudFormation, Terraform, etc.) directly from .NET script that is planned to be available as an API.
Predefined infrastructure contains: Route53 config, created API Gateway with custom domain.
Dynamic infrastructure contains: S3 and Lambda together with API Gateway modifications and deployment.
Once the bucket for a new service is created and the built app is pushed to the bucket, new Lambda function is created, configured and published. The last thing that is left is to create a new resource (route) that will invoke underlying Lambda function and its underlying routes (e.g. example.com/auth/register).
The issue is that when I create an integration on a non-root resource, Lambda function cannot be found or the Uri is not specified as it should be (this is something I am trying to figure out).
Here is the simplified code that I wrote to accomplish this (I will exclude Lambda function and S3 bucket created and show only API gateway and Lambda resource policy updates as they are relevant here). Important to note is that this code produces the same results as if it would be done via AWS console. Also, this code produces a working solution if the route is not specified (e.g. example.com/register)
var functionArn = await Lambda.GetFunctionArn(accessKey, secretKey, region, lambdaFunction);
var pathResponse = await c.CreateResourceAsync(new CreateResourceRequest
{
ParentId = rootId,
PathPart = path,
RestApiId = apiId
});
await c.PutMethodAsync(new PutMethodRequest
{
AuthorizationType = "NONE",
HttpMethod = "ANY",
ResourceId = pathResponse.Id,
RestApiId = apiId
});
var proxyResponse = await c.CreateResourceAsync(new CreateResourceRequest
{
ParentId = pathResponse.Id,
PathPart = "{proxy+}",
RestApiId = apiId
});
await c.PutMethodAsync(new PutMethodRequest
{
AuthorizationType = "NONE",
HttpMethod = "ANY",
ResourceId = proxyResponse.Id,
RestApiId = apiId
});
await Lambda.AddPermissions(account, accessKey, secretKey, region, lambdaFunction, apiId, path);
await c.PutIntegrationAsync(new PutIntegrationRequest
{
HttpMethod = "ANY",
IntegrationHttpMethod = "POST",
ResourceId = pathResponse.Id,
RestApiId = apiId,
PassthroughBehavior = "WHEN_NO_MATCH",
Type = IntegrationType.AWS_PROXY,
Uri = $"arn:aws:apigateway:{region}:lambda:path/2015-03-31/functions/{functionArn}/invocations"
});
await c.PutIntegrationAsync(new PutIntegrationRequest
{
HttpMethod = "ANY",
IntegrationHttpMethod = "POST",
ResourceId = proxyResponse.Id,
RestApiId = apiId,
PassthroughBehavior = "WHEN_NO_MATCH",
Type = IntegrationType.AWS_PROXY,
Uri = $"arn:aws:apigateway:{region}:lambda:path/2015-03-31/functions/{functionArn}/invocations"
});
var deployment = await c.CreateDeploymentAsync(new CreateDeploymentRequest
{
Description = $"API deployment to {environment}",
RestApiId = apiId,
StageName = environment
});
return deployment.Id;
where Lambda.AddPermissions is as follows:
var basePermission = await c.AddPermissionAsync(new AddPermissionRequest
{
Action = "lambda:InvokeFunction",
FunctionName = name,
Principal = "apigateway.amazonaws.com",
SourceArn = $"arn:aws:execute-api:{region}:{account}:{apiId}/*/*/{path}/*",
StatementId = Guid.NewGuid().ToString()
});
var proxyPermission = await c.AddPermissionAsync(new AddPermissionRequest
{
Action = "lambda:InvokeFunction",
FunctionName = name,
Principal = "apigateway.amazonaws.com",
SourceArn = $"arn:aws:execute-api:{region}:{account}:{apiId}/*/*/{path}",
StatementId = Guid.NewGuid().ToString()
});
return new List<string>
{
basePermission.Statement,
proxyPermission.Statement
};
Is there an issue with SourceArn specifications? I first created them through the AWS console (they are automatically created when the integration is created for Lambda) and they are the same.
Again, this all works when there is no path (non-root resource).

Passing parameter into WCF metadata

I have a WCF service with a class that implements IContractBehavior and IWsdlExportExtension that generates a WSDL with a user's allowed operations and excludes operations and types they do not have access to.
The limitation of this is however is that for each user, I have to manually change which user I am generating the WSDL for.
I'd like to fix this limitation by passing in the user as part of the request for the metadata for example.
localhost/service.svc?user=me
or
localhost:9766/service.svc?singleWsdl&user=me
Alternatively I could use svcutil would also work as long as the resulting WSDL is flattened.
I was able to get this to work by doing the following
I host the service in console application.
I added an endpoint with the IContractBehavior added to it, passing in the desired parameter into the IContractBehavior class.
After the service is open, I use WsdlExporter to export the Metatadaset
Finally I use the WsdlHelper to generate the Wsdl file as
described here
http://www.shulerent.com/2013/03/14/generating-a-single-flattened-wsdl-from-an-existing-wcf-service/
The code
const string BASE_ADDRESS =
"http://localhost:8731/Design_Time_Addresses/CalcService";
var uri = new Uri(BASE_ADDRESS);
var user = "userName";
using (var serviceHost = new ServiceHost(typeof(Calc), uri))
{
var exporter = new WsdlExporter();
var endpoint = serviceHost.AddServiceEndpoint(typeof(ICalc),
new BasicHttpBinding(), "");
endpoint.Contract.Behaviors.Add(new
RestrictedOperationsWsdlExportExtensionAttribute(user));
serviceHost.Open();
Console.WriteLine("The service is ready: " + user);
exporter.ExportEndpoint(endpoint);
if (exporter.Errors.Count == 0)
{
var metadataSet = exporter.GetGeneratedMetadata();
var asy= Assembly.GetAssembly(typeof(WsdlExporter));
var t = asy.GetType("System.ServiceModel.Description.WsdlHelper",
true);
var method = t.GetMethod("GetSingleWsdl",
System.Reflection.BindingFlags.Public
| System.Reflection.BindingFlags.Static);
var serviceDescription =
method.Invoke(null, new object[] {metadataSet})
as System.Web.Services.Description.ServiceDescription;
if (serviceDescription != null)
{
serviceDescription.Name = "Calc";
serviceDescription.Write(user + ".wsdl");
}
}
}

Dynamic Web Service Client

I have written a dynamic web service client. That works with SOAP 1.1 but fails with SOAP 1.2
When I use ServiceDescriptionImporter.Import I get the following warning:
OptionalExtensionsIgnored
Below is the code to prepare the web service:
using (var client = new WebClient())
{
//Trust all certificates
System.Net.ServicePointManager.ServerCertificateValidationCallback = ((sender, certificate, chain, sslPolicyErrors) => true);
client.Credentials = new NetworkCredential(#"domain\user","password");
using (var stream = client.OpenRead(url))
{
// Get a WSDL file describing the service.
ServiceDescription description = ServiceDescription.Read(stream);
// Initialize a service description importer.
ServiceDescriptionImporter importer = new ServiceDescriptionImporter();
importer.ProtocolName = webServiceProtocol.ToString();
importer.Style = ServiceDescriptionImportStyle.Client;
importer.AddServiceDescription(description, null, null);
// Report on the service descriptions.
Console.WriteLine("Importing {0} service descriptions with {1} associated schemas.",
importer.ServiceDescriptions.Count, importer.Schemas.Count);
// Add any imported files
foreach (System.Xml.Schema.XmlSchema wsdlSchema in description.Types.Schemas)
{
foreach (System.Xml.Schema.XmlSchemaObject externalSchema in wsdlSchema.Includes)
{
if (externalSchema is System.Xml.Schema.XmlSchemaImport)
{
Uri baseUri = new Uri(url);
Uri schemaUri = new Uri(baseUri, ((System.Xml.Schema.XmlSchemaExternal)externalSchema).SchemaLocation);
using (var schemaStream = client.OpenRead(schemaUri))
{
System.Xml.Schema.XmlSchema schema = System.Xml.Schema.XmlSchema.Read(schemaStream, null);
importer.Schemas.Add(schema);
}
Console.WriteLine(((System.Xml.Schema.XmlSchemaExternal)externalSchema).SchemaLocation);
}
}
}
// Generate a proxy client.
importer.Style = ServiceDescriptionImportStyle.Client;
// Generate properties to represent primitive values.
importer.CodeGenerationOptions = System.Xml.Serialization.CodeGenerationOptions.GenerateProperties;
// Initialize a Code-DOM tree into which we will import the service.
CodeNamespace nmspace = new CodeNamespace();
CodeCompileUnit unit1 = new CodeCompileUnit();
unit1.Namespaces.Add(nmspace);
// Import the service into the Code-DOM tree. This creates proxy code
// that uses the service.
ServiceDescriptionImportWarnings warning = importer.Import(nmspace, unit1);
Console.WriteLine("Warning: " + warning);
if (warning == 0 || warning == ServiceDescriptionImportWarnings.OptionalExtensionsIgnored)
{
// Generate and print the proxy code in C#.
CodeDomProvider provider1 = CodeDomProvider.CreateProvider("CSharp");
// Compile the assembly with the appropriate references
string[] assemblyReferences = new string[2] { "System.Web.Services.dll", "System.Xml.dll" };
CompilerParameters parms = new CompilerParameters(assemblyReferences);
CompilerResults results = provider1.CompileAssemblyFromDom(parms, unit1);
assembly = results.CompiledAssembly;
foreach (CompilerError oops in results.Errors)
{
Console.WriteLine("========Compiler error============");
Console.WriteLine(oops.ErrorText);
}
}
else
{
// Print an error message.
Console.WriteLine("Warning: " + warning);
}
}
}
If I ignore the warning and compile the code using CodeDomProvider it compiles with no errors. The problem is when I call a method from the web service I then get the following error:
Exception has been thrown by the target of an invocation. SOAP header
Action was not understood.
The code to call the method is below:
//Invoke the web service method
object service = GetAssembly().CreateInstance("BizTalkServiceInstance");
Type serviceType = service.GetType();
PropertyInfo propInfo = serviceType.GetProperty("Credentials");
propInfo.SetValue(service, new NetworkCredential("user", "pass", "domain"), null);
object request = GetObjectFromString(requestName, requestValue);
object response = serviceType.InvokeMember(methodName, System.Reflection.BindingFlags.InvokeMethod, null, service, new object[] { request });
Console.WriteLine(GetValueFromObject(responseName,response));
Console.ReadLine();
return null;
I really cannot work out what I am missing.
It looks like I was doing this the old way. I have switched over to ServiceContractGenerator. This now creates a Service Client object which provides a constructor which allows you to set the binding.
It now throws compile errors on one of my services but at least I have both 1.1 and 1.2 services working.
You can see the code and my problem with the new way of doing it here:
ServiceContractGenerator CodeDomProvider Compile Errors

Google OAuth 2.0 in .NET4 fails to create ServiceAccount on medium trust level server

I would like to read some calendar events inside a .NET4.0 web service. Because no user is involved I used a service account authorization method.
It works perfectly on my local machine. But when I want to install it on the hosting server I get permission exceptions during creating the certificate. The cause is the medium trust level of the web service. But that level is common on hosting servers.
Are there other ways to access the api that would work on a medium trust level server?
Here is my internal service code:
public static List<CalendarEventObject> GetEvents( string calendarName, int maxToGet, out string error )
{
string done = "";
error = "";
try
{
// access google api with service account
String serviceAccountEmail = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx#developer.gserviceaccount.com";
var p12File = System.Web.HttpContext.Current.Server.MapPath( "./bin/MyAppsServiceAccountFileFromGoogleDeveoperConsole.p12" );
done += "maped certificate: " + p12File;
// this needs System.Security.Permissions.KeyContainerPermission permission,
// that is not inside medium trust (nor inside high trust)
var certificate = new X509Certificate2( p12File
, "notasecret"
, X509KeyStorageFlags.MachineKeySet |
X509KeyStorageFlags.PersistKeySet |
X509KeyStorageFlags.Exportable ); // tried several storage flags with no success
ServiceAccountCredential credential = new ServiceAccountCredential(
new ServiceAccountCredential.Initializer( serviceAccountEmail )
{
Scopes = new[] { CalendarService.Scope.Calendar }
}.FromCertificate( certificate ) );
// Create the service.
var service = new CalendarService( new BaseClientService.Initializer( )
{
HttpClientInitializer = credential,
ApplicationName = "MyCalendarReader",
} );
done += "service created ";
// Fetch the list of calendar list
var list = service.CalendarList.List( ).Execute( ).Items;
done += "CalendarList loaded ";
if ( list.Count == 0 ) throw new Exception( "CalendarList returned none" );
bool found = false;
List<string> calendars = new List<string>( );
foreach ( var calendarListEntry in list )
{
calendars.Add( calendarListEntry.Summary );
if ( calendarListEntry.Summary == calendarName )
{
found = true;
return ExtractEvents( service, calendarListEntry, maxToGet );
}
}
if ( !found )
{
throw new Exception( "No matching calendar: " + String.Join( ";", calendars ) );
}
}
catch ( Exception ex )
{
error = String.Format( "\nDone:{0}\n msg:{1}\n inner:{2}\n stack:{3}", done, ex.Message, ex.InnerException != null ? ex.InnerException.Message : "none", ex.StackTrace );
}
return new List<CalendarEventObject>( );
}
I don't think that there's a solution to your problem other than changing the trust level.
The "medium trust level is common on hosting servers" statement, while it used to be true - it's not anymore.
Medium Trust has been made obsolete by the ASP.NET team. The main reason is that reflection started to be used a lot more in the last couple of years. To do reflection in Medium Trust is almost impossible.
Your best solution, especially for the long run, is to tell this to your hosting provider and to just find another provider in case they refuse to offer you Full Trust.
PS: ASP.NET Partial Trust does not guarantee application isolation

Authentication to FreshBooks via DotNetOpenAuth

I'm trying to use OAuth for authentication for the FreshBooks API from my ASP.NET MVC C# app. Here is what I have so far:
I'm using DotNetOpenAuth here is the code I have in my controller action
if (TokenManager != null)
{
ServiceProviderDescription provider = new ServiceProviderDescription();
provider.ProtocolVersion = ProtocolVersion.V10a;
provider.AccessTokenEndpoint = new MessageReceivingEndpoint ("https://myfbid.freshbooks.com/oauth/oauth_access.php", DotNetOpenAuth.Messaging.HttpDeliveryMethods.PostRequest);
provider.RequestTokenEndpoint = new DotNetOpenAuth.Messaging.MessageReceivingEndpoint("https://myfbid.freshbooks.com/oauth/oauth_request.php", DotNetOpenAuth.Messaging.HttpDeliveryMethods.PostRequest);
provider.UserAuthorizationEndpoint = new DotNetOpenAuth.Messaging.MessageReceivingEndpoint("https://myfbid.freshbooks.com/oauth/oauth_authorize.php", DotNetOpenAuth.Messaging.HttpDeliveryMethods.GetRequest);
provider.TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new HmacSha1SigningBindingElement() };
var consumer = new WebConsumer(provider, TokenManager);
var response = consumer.ProcessUserAuthorization();
if (response != null)
{
this.AccessToken = response.AccessToken;
}
else
{
// we need to request authorization
consumer.Channel.Send(consumer.PrepareRequestUserAuthorization(
new Uri("http://localhost:9876/home/testoauth/"), null, null));
}
}
The TokenManager is the same class that is provided with the DotNetOpenAuth sample, I've set my consumer secret that FreshBooks gave me.
On the consumer.Channel.Send(consumer.PrepareRequestUserAuthorization(...)) I've got the following exception:
"The remote server returned an error: (400) Bad Request.".
Am I doing this correctly? Based on FreshBooks documentation and DotNetOpenAuth samples that should work correctly.
Is there a simpler way to authenticate with OAuth, as DotNetOpenAuth is a bit huge for simply using OAuth authentication?
if you want to use DotNetOpenAuth you need to make sure that:
you use signature method "PLAINTEXT"
and use PlaintextSigningBindingElement as TamperProtectionElements
something like this works for me:
public static readonly ServiceProviderDescription ServiceDescription = new ServiceProviderDescription
{
ProtocolVersion = ProtocolVersion.V10a,
RequestTokenEndpoint = new MessageReceivingEndpoint(oAuthBase + "/oauth_request.php", HttpDeliveryMethods.PostRequest),
UserAuthorizationEndpoint = new MessageReceivingEndpoint(oAuthBase + "/oauth_authorize.php", HttpDeliveryMethods.GetRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
AccessTokenEndpoint = new MessageReceivingEndpoint(oAuthBase + "/oauth_access.php", HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest),
TamperProtectionElements = new ITamperProtectionChannelBindingElement[] { new PlaintextSigningBindingElement() }
};
public static void RequestAuthorization(WebConsumer consumer)
{
if (consumer == null)
{
throw new ArgumentNullException("consumer");
}
var extraParameters = new Dictionary<string, string> {
{ "oauth_signature_method", "PLAINTEXT" },
};
Uri callback = Util.GetCallbackUrlFromContext();
var request = consumer.PrepareRequestUserAuthorization(callback, extraParameters, null);
consumer.Channel.Send(request);
}
You could try using my open source OAuth Library. It's extremely simple to use and get going. I have a sample project that's available in the download that connects to Google, Twitter, Yahoo and Vimeo. I've intentionally kept the code very simple so it's easy to understand.
OAuth C# Library
I've not used FreshBooks, but it should be a simple matter of changing the url for one of the providers in the sample application and of course setting up provider specific keys etc.

Categories

Resources