I wanted to share a proof-of-concept approach to creating a simple application layer firewall with event mapping in PeopleSoft. This post is similar to my Using the Event Mapping Framework to Enforce Two-Factor Authentication post, but this post highlights more on the general idea of using event mapping to extend the delivered PeopleSoft security model. What I will be showing is how you can conditionally reject requests to specific resources based on the IP address that the user is coming from. This is a simple way to add an additional layer of security to your PeopleSoft applications with very little overhead. This functionality will be accomplished with event mapping, which is a new feature in the 8.55 PeopleTools.
Here are the steps that you will need to take to achieve this functionality:
Step 1: Defining the Protected Resources
You will need to determine the content references that you want to enable location-based security on. A good example would be the administrative or non-self-service components. However for this example, I am choosing to protect the “Change My Password” content reference.
Step 2: Defining the Trusted IP Addresses
In this step, you will need to define a trusted IP address range (or ranges). The trusted IP address range will be the IP addresses that a user must be on in order to access the protected resources defined in step 1. If the user’s IP address falls outside of the trusted IP address range, then the user’s IP address will not be trusted. In this example, I created a custom page that has a grid to store the trusted IP address ranges.
I created a table in the database to store the information.
Step 3: Writing Application Code
Now I will provide you with some application class PeopleCode to enforce the additional layer of security. I created an application package named PSM_APP_FIREWALL and inserted an application class named BlockIP.
The BlockIP class will need to implement the PT_RCF:ServiceInterface base class in order to be used with event mapping. The BlockIP class code checks if a user’s IP address is within the trusted IP address ranges defined in the database table mentioned in step 2. The code will throw an error and (optionally) redirect the user if the user’s IP address is not trusted. Here is the code.
import PT_RCF:ServiceInterface; class BlockIP extends PT_RCF:ServiceInterface method Execute(); end-class; method Execute /+ Extends/implements PT_RCF:ServiceInterface.execute +/ /* code to check if a user's IP address in within a given range of IP addresses */ Local number &i, &x, &y, &z; Local string &myipnum, &Value1, &fromipnum, &Value2, &toipnum; Local string &trusted = "false"; rem get user's IP address; Local string &ip = %Request.RemoteAddr; rem split the ip into 4 parts; Local array of string &myip = Split(&ip, "."); rem if the IP is in 4 parts then pad each part with up to three zeros; If (&myip.Len = 4) Then For &i = 1 To 4 rem pad each part of the ip with zeros to make a total of 3 digits; &myipnum = &myipnum | Rept("0", 3 - Len(&myip [&i])) | &myip [&i]; End-For; End-If; rem get the ranges of trusted IP addresses; Local Rowset &rs1; &rs1 = CreateRowset(Record.PSM_TRUSTED_IP); &rs1.Fill("where 1=1"); rem loop through each IP range; For &x = 1 To &rs1.ActiveRowCount rem get the lower IP of the range first and split the ip into 4 parts; &Value1 = &rs1(&x).PSM_TRUSTED_IP.PSM_FROM_IP.Value; Local array of string &fromip = Split(&Value1, "."); rem if the IP is in 4 parts then pad each part with up to three zeros; If (&fromip.Len = 4) Then &fromipnum = ""; For &y = 1 To 4 rem pad each part of the ip with zeros to make a total of 3 digits; &fromipnum = &fromipnum | Rept("0", 3 - Len(&fromip [&y])) | &fromip [&y]; End-For; End-If; rem get the higher IP of the range second and split the ip into 4 parts; &Value2 = &rs1(&x).PSM_TRUSTED_IP.PSM_TO_IP.Value; Local array of string &toip = Split(&Value2, "."); rem if the IP is in 4 parts then pad each part with up to three zeros; If (&toip.Len = 4) Then &toipnum = ""; For &z = 1 To 4 rem pad each part of the ip with zeros to make a total of 3 digits; &toipnum = &toipnum | Rept("0", 3 - Len(&toip [&z])) | &toip [&z]; End-For; End-If; If (Value(&myipnum) >= Value(&fromipnum) And Value(&myipnum) <= Value(&toipnum)) Then &trusted = "true"; &x = &rs1.ActiveRowCount; End-If; End-For; rem if the ip is not trusted then block the request; If (&trusted = "false") Then rem get url to page that explains the location-based security policy (optional); rem Local string &Block_Request = GenerateComponentPortalURL(%Portal, %Node, MenuName.PSM_APP_FIREWALL, "GBL", Component.PSM_BLOCK, "PSM_BLOCK", ""); rem redirect to the url (optional); rem %Response.RedirectURL(&Block_Request); rem use error to stop processing. this will prevent data being communicated to client; Error MsgGet(40, 20, "You are not authorized to access this component."); End-If; end-method;
Step 4: Defining a Related Content Service
Once the application class is written, you will need to tie the application code to a service that will be used to map the code to component events. To create a service you will need to navigate to PeopleTools -> Portal -> Related Content Service -> Define Related Content Service. Here you will add a new service ID. Be sure to select “Application Class” as the URL type and specify the package name, path, and class name.
Step 5: Mapping the Event
Last you will need to map the newly created service to the content references that you want to protect with location based security. To do this, navigate to PeopleTools -> Portal -> Related Content Service -> Manage Related Content Service. Here you will need to select the “Event Mapping” tab and then click the “Map the event of the Application pages” link.
This is where you will select the content reference that you want to protect. As I previously said, I will be mapping my code to the “Change My Password” content reference.
The previously created service will need to be mapped to the component level “Pre Build” event with a “Pre Process” processing sequence.
This step will need to be performed for all of the content references that you want to map the service to. Important Note: If you are experiencing inconsistent behavior with your mapped code not firing, then take a look at this MOS document: INCONSISTENT BEHAVIOR OF EVENT MAPPING (Doc ID 2171391.1).
Step 6: Test
Side Note: At this point, Oracle does not offer the ability to map events to the SearchInit event of the search record. Hopefully Oracle can provide this functionality in the future. Having this ability would prevent having to wait until after the search for the custom event to fire.
For a better user experience, I suggest redirecting the user to a separate page along with throwing the error in the BlockIP class. For this example, I redirect the users with untrusted IP addresses to page with some static text.
If you are interested in removing the Navigator menu item references to the protected resources that you have defined, then i suggest checking out this post on Location-Based Menu Pruning. This will help enhance the user experience, because the users will not be presented with menu items to the resources that you are protecting with location-based security.
The event mapping framework has brought the ability to create an easy to configure, bolt-on application layer firewall that would’ve been infeasible to implement in previous PeopleTools versions. This same idea of conditionally rejecting requests to resources can be applied to other conditions such as time of day, request cookie values, request header values, etc. If you have any questions, comments, or ideas of ways to leverage event mapping, then feel free to comment below.