Exposing the JobServerImpl Class as a Web Service

Exposing the JobServerImpl Class as a Web Service

The JobServer and JobClient application designs are similar in many ways to traditional DCOM application designs. This example has a high degree of coupling in that we assume both client and server run under the common language run­time. Running all remoting pieces under the common language runtime means that we can take advantage of the full .NET Framework type system, choose the most efficient communication options, and enjoy a common environment for developing code. We also assume that a firewall doesn’t separate the JobServer and JobClient applications (as a DCOM solution would require). These assumptions afford us a lot of freedom in making design decisions, and this flexibility is fine for intranet or private network solutions. If we design .NET Remoting applications with these assumptions in mind, we can gain the power and performance of DCOM with a far simpler and far more extensible programming model.

Over time, however, the requirements of the internal application might change, requiring that we expose all or part of the system to the outside world. This usually means introducing variables such as the Internet; firewalls; and unknown, uncontrolled clients that aren’t necessarily running under the common language runtime. If the server application is implemented by using DCOM, making the transition isn’t at all straightforward. The usual solution is to write Active Server Pages (ASP)—or now, ASP.NET—code to consume the DCOM objects and to provide an HTML-based interface for the outside world. However, outside parties might want to consume the services of the internal application directly, to integrate the services with their own application or to use their own visual interface. If the internal services are exposed only as human-readable HTML, integration with other applications is difficult and not resilient to change. The usual technique is for outside developers to resort to screen scraping or parsing the HTML that’s meant only for visual communication. This technique is notoriously fragile because any revisions to the published HTML UI can break the application. Also, visual updates to Internet applications are very common. For many business-to-business scenarios and single business applications, exposing services directly is a far more powerful, flexible, and maintainable approach than hard-coding a visual HTML interface.

Herein lies the power of Web Services. Web Services provide a way to expose component services over the Internet rather than just present visual information. These services can be assembled to build larger applications that are similar to traditional software components. Web Services are high-level abstractions that accomplish this feat by using a number of collaborating technologies, such as SOAP; HTTP; XML; Web Service Description Language (WSDL); and Universal Description, Discovery, and Integration (UDDI). Basing Web Services on HTTP and SOAP makes the service communication firewall friendly. WSDL describes the Web Service public methods, parameters, and service location, and UDDI provides a directory for locating Web Services (WSDL files) on the Internet.

NOTE
WSDL is an XML grammar for describing Web Services in a platform-independent and language-independent manner. A WSDL document allows clients on any platform to locate a Web Service running on any platform and invoke its publicly exposed methods. Thus, WSDL is similar to Interface Definition Language (IDL), except that WSDL also specifies the location (URL) of the service. WSDL tools enable developers to generate a .wsdl file for a given Web Service and to automate creating client code to call those services. The .NET Framework provides two such tools: WSDL.exe and SOAPSuds.exe. SOAPSuds.exe is used with .NET Remoting–based Web Services, while WSDL.exe supports only ASP.NET-based Web Services.

All these underlying technologies are open and in various stages of draft or approval by the W3C. (See http://www.w3c.org for more information.) The Web Service vision dictates that as long as vendors comply with a set of open standards, anyone with standards-compliant tools can create a Web Service with any language on any platform and consume it with any language on any platform. Because of the flux in some of the nonfinalized underlying standards and the speed with which vendors update their tools, this universal interoperability has been realized only partially at the time of this writing. For more information about writing Web Services that have the best chance of interoperability, see the MSDN article “Designing Your Web Service for Maximum Interoperability.”

Constraints of Web Services

As we’ve stated, Web Services can interoperate with clients running on any platform, written in virtually any language, and Web Services can get through firewalls. So why not use them all the time? The answer is that to achieve this interoperability, you lose some of the rich functionality .NET Remoting offers. Just as IDL or type libraries provide least-common-denominator descriptions of interface contracts and data types for COM objects, WSDL is a compromised description of calling syntax and data types for remotable objects. .NET Remoting provides full common language runtime type fidelity, while Web Services are limited to dealing with generic types that can more easily map to the type systems of many languages.

Because of WSDL’s type description limitations, describing .NET remote objects by using WSDL requires us to avoid using properties. Although the object can still support properties for other .NET clients, Web Service clients must have another mechanism to get and set this data.

Being firewall friendly involves more than simply running over HTTP. Client callbacks can be problematic because they require initiating a connection to the client. Because the client becomes the server in callback scenarios, clients behind firewalls can’t service connections made to ports that aren’t open on the firewall. Although workarounds for these cases exist, these workarounds won’t solve all issues when clients are behind Network Address Translation (NAT) firewalls. Furthermore, these workarounds aren’t appropriate for most Web Service clients because of the proprietary nature of the required custom client code. Because these hardware firewalls can perform IP address translation, a server can’t make a callback to a client based on its given IP address.

Changes to the Sample Application

To test the JobServer Web Service, we’ll use a modified version of the JobClient Windows Forms application. Although our test client runs under the common language runtime, any non-.NET client with proper vendor tool support for creating callable client code from the Job Server’s WSDL file should be able to access the JobServer Web Service.

This following discussion will summarize the changes to the job application design and implementation needed to fully support Web Services.

Removing Client Callback

Because our Web Service won’t support client callbacks, we need another way to find out about job updates. To do this, we’ll poll for the data by calling the GetJobs method every 5 seconds. This is a very common technique for Web-enabled client applications, and it’s easy to set up by using the Windows Forms Timer control. Polling for data also simplifies the client application quite a bit. Instead of updating the JobInfo list with new jobs incrementally, we get the entire JobInfo list on every poll. Also, because no client event is exposed, there’s no secondary thread and no need to update the UI by using the Invoke method.

Selecting an Activation Method

We usually implement Web Services as SingleCall-activated servers that manage state by using some persistent data store such as a database. As a simplifying assumption, we’ll use the Singleton activation mode and retain state in memory. It’s also possible to implement a stateful Web Service by using client-activated objects, but that might make it difficult or impossible for some non-.NET clients to use our service.

Now that the JobServer application is Web Service compliant, we need to properly configure IIS to host the JobServerImpl object. These steps aren’t specific to exposing Web Services but are used in hosting any remote object in IIS.

Configuring the Virtual Directory

To configure the virtual directory, you first create a new Web application by running the IIS Microsoft Management Console (MMC) snap-in. Select the Default Web Site tree node, choose Action/New/Virtual Directory, and name the alias for the virtual directory JobWebService. After we finish configuring the wizard, we need to properly configure security for the Web application. By default, IIS configures newly created Web applications to use Windows Integrated authentication (NTLM authentication). This will make our application unreachable by non-Windows clients and users who don’t have sufficient NTFS rights to the physical directory that JobWebService is aliasing. To make our Web Service available to all clients, we’ll “turn off” security by configuring anonymous access. (We’ll turn security on again in the section “Adding Security to the Web Service.”) First, select the JobWebService application in the Default Web Site tree node. Choose Action/Properties. In the tabbed dialog box, choose Directory Security and click the Edit button. Uncheck Integrated Windows Authentication, and check Anonymous Access.

Configuring the Web.config File

Now we need to modify the configuration file details. You use the Web.config file located in the root of the hosting application’s virtual directory to configure an IIS-hosted remote application, as the following listing shows:

<configuration>
  <system.runtime.remoting>
    <application>
      <service>
        <wellknown mode="SingleCall" 
          type="JobServerLib.JobServerImpl,JobServerLib" 
          objectUri="JobServer.soap" />
     </service>
    </application>
  </system.runtime.remoting>
</configuration>

The <wellknown> element is very similar to the same-named element used in the earlier applications’ configuration files. The notable addition is that the objectUri attribute has the .soap extension. IIS-hosted well-known objects must have object URIs that end in either .rem or .soap.

Note that there’s no <channel> element in this example, and recall that the previous examples used this tag to configure a channel type and set the port. The default channel for remoting objects hosted in IIS is HttpChannel, which is also required by Web Services. This HttpChannel automatically uses the same port IIS is configured to use (port 80 by default). To configure IIS to use a different port, run the IIS MMC snap-in, select Default Web Site, choose Action/Properties, and set the port under the Web Site tab.

Deployment

IIS-hosted remote objects must be placed in either the virtual directory’s \bin directory or the global assembly cache (GAC). For simplicity, we’ll deploy the JobServerImpl object to the \bin directory.

Using the SOAPSuds Tool

SOAPSuds is Microsoft’s tool for extracting descriptions from .NET Remoting–based Web Services in a variety of formats. SOAPSuds can be run against a local assembly or an IIS-hosted .NET Remoting object endpoint. You have four main choices for output format:

  • Assembly with implementation

  • Metadata-only assembly

  • XML schema (WSDL)

  • Compilable class

Let’s look at each of these formats now.

Assembly with Implementation

The following command generates an assembly containing the implementation of the JobServerLib assembly. First, you can run SOAPSuds directly against the local JobServerLib assembly by using the –types option:

Soapsuds –types:JobServerLib.JobServerImpl,JobServerLib Œ
         –oa:JobServerLib.dll

Here’s the syntax for the –types option:

Namespace.ClassName,AssemblyName

The –oa option (short for output assembly) causes the tool to generate an assembly containing the implementation of the JobServerImpl class.

A more interesting case occurs when you run SOAPSuds against the IIS-hosted endpoint for the remote object:

Soapsuds –url:http://localhost/JobWebService/JobServer.soap?wsdl Œ
         –oa:JobServerLib.dll 

Metadata-Only Assembly

SOAPSuds also provides a simple way to generate a metadata-only assembly. This is the syntax for doing so on the JobWebService application:

Soapsuds –url:http://localhost/JobWebService/JobServer.soap?wsdl Œ
         –oa:JobServerLib.dll

Many developers consider this technique the easiest way to generate an assembly containing only the minimum calling syntax needed for a .NET Remoting client.

XML Schema (WSDL)

Generating a .NET assembly of course is useful only for supporting .NET clients. We also need a way to create a WSDL description of our Web Service to support non-.NET clients. The –os flag tells SOAPSuds to output a schema file to describe the Web Service. Here’s the syntax:

Soapsuds –urll:http://localhost/JobWebService/JobServer.soap?wsdl Œ
         –os:JobServerLib.wsdl

For the record, you can obtain the same WSDL representation of the Web Service by browsing to the endpoint via Microsoft Internet Explorer. Simply enter this command into the browser’s address bar:

http://localhost/JobWebService/JobServer.soap?wsdl

Next right-click the client area of the browser, and select View Source from the context menu. This file is functionally identical to the WSDL file generated by SOAPSuds. You can run this resulting file through a supporting tool to create a callable proxy wrapper. Non-.NET clients can use this technique to create client code to interoperate with .NET Remoting–based Web Services.

Compilable Class

SOAPSuds can also convert a WSDL file into compilable .NET source code by using the input schema flag (–is) and the generate code flag (–gc):

Soapsuds –is:JobServer.wsdl –gc

Or SOAPSuds can convert the WSDL file directly into an assembly:

Soapsuds –is:JobServer.wsdl –oa:JobServerLib.dll

NOTE
You might recall from Chapter 2 that the HttpChannel serializes message objects by using a SOAP wire format by default. As its name implies, SOAPSuds is primarily intended to generate metadata for objects hosted within .NET Remoting servers that use HttpChannel. This is because SOAPSuds’ default behavior is to generate what is known as a wrapped proxy. A wrapped proxy contains a hard-coded server URL and supports using only the HttpChannel, which is convenient for our Web Service client. However, SOAPSuds can also generate a nonwrapped proxy that supports the TcpChannel and allows the server URL to be explicitly set by calling code. You can generate a nonwrapped metadata assembly by using the -nowp option:

Soapsuds –ia:InputAssemblyName -oa:OutputAssemblyName.dll –nowp

When using the generated output metadata assembly, you must specify the server URL and desired channel either programatically or via a configuration file.

Adding Security to the Web Service

As we discussed earlier, easy security configuration is one of the best reasons to choose IIS as the hosting environment for .NET Remoting applications. You configure security for .NET Remoting–based Web Services the same way as you configure security for all IIS-hosted remote objects. Thus, you can follow these same steps to configure security for other IIS-hosting scenarios, such as client-activated objects and objects using formatters that aren’t Web Service compliant, such as the binary formatter.

The following example shows how to configure the JobServer Web Service to use IIS NTLM. This way, IIS will authenticate the JobClient requests based on Windows NT credentials. In simple terms, the JobServerImpl object will be accessible only to clients that can supply credentials with sufficient NTFS rights to the JobWebService virtual directory. Note that NTLM is suitable only for intranet scenarios. This is because clients must have Windows credentials and NTLM authentication isn’t firewall friendly or proxy-server friendly.

Changes to the Virtual Directory Settings

Select the JobWebService application in the Default Web Site tree node. Choose Action/Properties. In the tabbed dialog box, choose Directory Security and click Edit. This time, check Windows Integrated Authentication and uncheck Anonymous Access.

Changes to the Web.config File

Add the following lines to the JobWebService application’s Web.config file:

<system.web>
  <authentication mode="Windows"/>
  <identity impersonate="true"/>
</system.web>

These options configure ASP.NET to use Windows authentication and to impersonate the browsing user’s identity when making server-side requests.

Changes to the JobClient Application

.NET Framework clients can obtain credentials (user name, password, and domain name, if applicable) to submit to the server in two ways: default credentials and explicit credentials.

The default credentials concept is simply to obtain the user name and password of the currently logged-on user without requiring—or possibly allowing—the credentials to be explicitly specified. To configure the client to use default credentials via the client configuration file, add the useDefaultCredentials attribute to the HTTP channel, as shown here:

<channels>
  <channel ref="http" useDefaultCredentials="true"/>
</channels>

To specify default credentials at run time, set the useDefaultCredentials property of the channel in the channel constructor:

IDictionary props = new Hashtable();
props["useDefaultCredentials"] = true;

HttpChannel channel = new HttpChannel(
        props,
        null,
        new SoapServerFormatterSinkProvider()
        );

Instead of automatically passing the interactive user’s credentials to the server, you might want to explicitly control the user name, password, and domain name. Because using explicit credentials also results in sending the supplied password across the wire in cleartext, this option should be used only in conjunction with some sort of encryption, such as Secure Sockets Layer (SSL).

You specify explicit credentials at run time by setting properties on the channel sink as follows. (We’ll discuss channel sinks in more detail in Chapter 7, “Channels and Channel Sinks.”)

IJobServer obj = (IJobServer)Activator.GetObject( typeof(IJobServer),
                 "http://localhost/JobWebService/JobServer.soap" );

ChannelServices.GetChannelSinkProperties(obj)["username"] = "Bob";
ChannelServices.GetChannelSinkProperties(obj)["password"] = "test";
ChannelServices.GetChannelSinkProperties(obj)["domain"] = "";

Of course, in a real-world application you wouldn’t want to hard-code credentials as shown here. Instead, you’d probably get the credentials from the user at run time or from a secure source.

This completes the steps necessary to secure the JobServer Web Service from unauthorized users. Although the Web Service is now secure, we can add an additional refinement to our authorization scheme. As configured, a user has either full access to the job assignment application or no access at all. However, we might want to support different access levels within our application, such as allowing only administrators to delete uncompleted jobs. The .NET Framework’s role-based security allows us fine-grained control over which users can access server resources.

Using Role-Based Security with .NET Remoting

Anyone who’s had the responsibility of managing Windows security for more than a trivial number of users is familiar with the usefulness of groups. Groups are powerful because access control to resources such as files and databases, as well as most operations, almost never needs to be as fine-grained as a per-user basis. Instead, users frequently share certain access levels that you can categorize into roles or groups.

Because this role-based or group-based approach is such a powerful abstraction for network administrators, it makes sense to use it to manage security in appli­cations development. This is what role-based security is all about: it’s an especially effective way to provide access control in .NET Remoting applications. Once you’ve authenticated a client, unless all authenticated clients have full access to the resources exposed by your application, you need to implement access control. As with all .NET Remoting security scenarios, using role-based security with .NET Remoting requires that you use IIS as the hosting environment.

When properly configured for security, a hosted remote object can use the current thread’s Principal object to determine the calling client’s identity and role membership. You can then allow or deny code to run based on the client’s role. You can control access by using three programming methods:

  • Declarative

  • Imperative

  • Direct principal access

Declarative programming means that you declare your intentions in code via attributes that are compiled into the metadata for the assembly. By using the System.Security.Permissions.PrincipalPermissionAttribute class and the System.Security.Permissions.SecurityAction enumeration, you can indicate that the calling client must be in a certain role to run a method:

[PrincipalPermissionAttribute(SecurityAction.Demand, 
Role="BUILTIN\\Administrators")]
    public void MySecureMethod()
    {
         
    }

If the client calling MySecureMethod isn’t in the BUILTIN\Administrators group, the system throws a System.Security.SecurityException.

Imperative programming is the traditional programming technique of putting the conditional role membership check in the method body, as shown here:

PrincipalPermission AdminPermission = 
                   new PrincipalPermission("Allen", "Administrator");
 
AdminPermission.Demand();

To use imperative role-based security, you create a System.Security.Permissions.PrincipalPermission object by passing in a user name and a role name. When you call the PrincipalPermission object’s Demand method, code flow will continue if the client is a member of the specified role; otherwise, the system will throw a System.Security.SecurityException.

You might not want to throw exceptions when checking for role membership. Throwing exceptions can hurt the performance of a remote application, so you should throw them only in exceptional circumstances. For example, you wouldn’t want test a user’s role membership by instantiating several Principal­Permission objects and catching the inevitable exceptions. Instead, declarative and imperative security techniques are best used if you expect the user to have the requested permission and you are verifying this expectation.

If you need to test a principal for role membership and there’s a high probability that the call will fail, you should use the direct principal access technique. Here’s an example:

IPrincipal ClientPrincipal = Thread.CurrentThread.CurrentPrincipal;
If ( ClientPrincipal.IsInRole( "Administrator") )
{
    RunMe();
     

Thread.CurrentThread.CurrentPrincipal returns an IPrincipal interface that represents the client’s identity. This interface provides an IsInRole method that will test the principal for role membership and return a Boolean value.

As we just mentioned, you must use IIS as the hosting environment to access role-based security. When you configure IIS to impersonate the client and to use a certain authentication scheme, the identity of the client will flow across the .NET Remoting boundary. Because you can’t authenticate a .NET Remoting client by using a different host, such as a Windows service, Thread.CurrentThread.CurrentPrincipal will contain an empty GenericIdentity. Because you can’t get the client’s identity, you can’t use access control. Therefore, when designing remote objects, you should think carefully about the expected hosting application type. You don’t want to expose sensitive public remote methods to unauthenticated clients.

Effects of Code Access Security on Remoting

When you run a program on your computer, you fully trust that the code won’t do anything malicious. Nowadays, code can come from many sources less trustworthy than the code you obtain from software purchased in shrink-wrapped packages, most notably from the Internet. What we really need are varying degrees of trust that depend on the code being accessed, rather than complete trust or no trust whatsoever. This is the purpose of .NET Code Access Security. The .NET Framework Code Access Security subsystem offers a flexible, full-featured way to control what permissions code requires to run, and it enforces constraints on code coming from various zones.

We won’t spend any more time talking about Code Access Security in this book for the simple reason that Code Access Security doesn’t work with .NET Remoting. Therefore, you should have a high degree of trust between client and server in a .NET Remoting application.