This command is a personal favorite of mine, not just because it's uniquely useful but because it was the first major piece of code that I wrote in the binary all by my lonesome. :-)
This SSJS filter gives you a real-time hook into any change of a user record, in much the same way that the *editFilterPre and -Post give you a real-time hook into changes of nodes in the folder/discussion/message hierarchy. When is this useful? Well, originally it was implemented to keep an external authentication database in sync with Webcrossing's database. For various reasons the client needed to be able to change user data from either direction so we wrote this filter so that it would fire whenever a user changed any field in their preferences or any other field in the user record was changed by a system process.
The filter also proved useful in a realtime forensics situation where a client suspected unauthorized access; with a userChangeFilterPost filter configured to send alerts it was possible to watch events as they unfolded rather than spelunking the logs afterwards.
The filter works simply: when it fires, two special globals are defined: changedUserId is the userId that was changed and changedFieldName is the name of the property that was altered; that includes both the built-in and custom properties. So the basic syntax is
var theUser = User.open(changedUserId);
theUser[changedFieldName]
to access the altered property. (Note the brackets syntax; theUser.changedFieldName treats the variable as a string literal which is not what you want.)
Like always, the User.open should be inside a try/catch structure because if the user has been deleted by some other process, it will throw an exception and halt processing.
Join us for a grand tour of Webcrossing - it's our favorite platform for web application development, and we're dying to tell you why we think so.
Showing posts with label advanced. Show all posts
Showing posts with label advanced. Show all posts
Passing clientside data to the server with AJAX
Sue wrote a post about mixing clientside and serverside Javascript in Webcrossing. I want to touch briefly on a related topic, which is passing data back and forth between client- and server-side. It can get confusing, especially when you're writing in essentially the same language throughout, to remember what context you're in and what you have access to and what you don't.
Where things can get really wacky is when you're using AJAX calls to change the contents of the browser window without a page refresh. Specifically, you have to remember that any local server-side variables you may have set outside of the AJAX'd content won't be there once you make an AJAX request. Here's a simple example, where I'll use WCTL to make it a bit easier to separate client from server.
First, let's assume your page has the following AJAX call on it. (This example uses jQuery, the Javascript framework with which I'm most familiar.)
function whenYouClick() {
$.get("/someNewContent",,function(newText) {
$('#newContentGoesHere').html(newText);
Where things can get really wacky is when you're using AJAX calls to change the contents of the browser window without a page refresh. Specifically, you have to remember that any local server-side variables you may have set outside of the AJAX'd content won't be there once you make an AJAX request. Here's a simple example, where I'll use WCTL to make it a bit easier to separate client from server.
First, let's assume your page has the following AJAX call on it. (This example uses jQuery, the Javascript framework with which I'm most familiar.)
function whenYouClick() {
$.get("/someNewContent",,function(newText) {
$('#newContentGoesHere').html(newText);
});
};
If you're not familiar with jQuery, just take my word for it that this code will call the WCTL macro or WCJS command "someNewContent", and whatever it returns will replace the current contents of the DOM element with ID "newContentGoesHere".
Now, let's move down into the body of the page...
%% set initValue 25 %%
<div id="newContentGoesHere">
%% use someNewContent %%
</div>
And then, separate from this page macro, you have this macro:
%% macro someNewContent %%
%% set returnVal 2*initVal %%
%% returnVal %%
%% endmacro %%
It should be obvious what will happen when you first load this entire page. someNewContent will execute and evaluate to "50" which will become the content of the div. But now, what happens if the clientside function whenYouClick() makes the AJAX call? someNewContent will execute again, but outside of the context of the entire page, that local variable initVal is not defined. returnVal will be 0.
As long as you keep it straight in your head which data is available where, this problem is not difficult to avoid. Here are two common approaches:
- You can store needed data in session variables. If you used session.initVal instead of the local variable, it would still be available inside the AJAX call (with some caveats that affect guest users who, depending on system settings, may not have a unique session).
- Alternatively, you can pass the data as arguments in your clientside function (or put it inside a DOM element where your clientside code can retrieve it) and then through to your AJAX call. Once your AJAX code is executing, any arguments you pass will be part of the form object.
Code snippet: What's up, DOCTYPE?
In a previous post I mentioned using AJAX to call server-side functions and write dynamic content to the page. Here I introduce an easy workaround for one of the places where Webcrossing trips over its own flexibility.
There's a system setting to include a DOCTYPE in every page response for compliance with W3C standards. But once defined, this happens automatically for every request, including those which are not intended to be a full page of HTML, such as the response to an AJAX call. In order to avoid breaking your page, use this simple clientside Javascript function to remove the DOCTYPE:
function removeDoctype( txt ) {
return txt.replace(/<!DOCTYPE[^>]+>/g, "");
}
Implemented with a jQuery AJAX call, it looks like this:
$.get("/someNewContent",,function(newText) {
$('#newContentGoesHere').html(removeDoctype(newText));
});
A colleague of mine likes to say, "Every so often a programmer will determine that the best solution for a problem is a regular expression. Now she has two problems."
There's a system setting to include a DOCTYPE in every page response for compliance with W3C standards. But once defined, this happens automatically for every request, including those which are not intended to be a full page of HTML, such as the response to an AJAX call. In order to avoid breaking your page, use this simple clientside Javascript function to remove the DOCTYPE:
function removeDoctype( txt ) {
return txt.replace(/<!DOCTYPE[^>]+>/g, "");
}
Implemented with a jQuery AJAX call, it looks like this:
$.get("/someNewContent",,function(newText) {
$('#newContentGoesHere').html(removeDoctype(newText));
});
A colleague of mine likes to say, "Every so often a programmer will determine that the best solution for a problem is a regular expression. Now she has two problems."
Authentication Filters - Part 2: Cookies
Cookies, Cookies, Cookies - can't have a decent web experience without them. In the past, cookies got a bad name and went through a period where they were completely unusable to track users on a website.
Times have changed and cookies are no longer the evil baked goods of old. Cookies are one of the ways that web servers can track the stateless HTTP requests across multiple pages. WebCrossing can utilize this information that might be passed from a main web server like this graphic illustrates:

Visitor comes to site, main web server sets cookie, visitor navigates to WebCrossing forums, reads cookie, sends cookie information to main web server and gets validation in return.
This is a very common method of authentication and it is performed in the authentication filter like so. We will begin with just getting the cookie in the first place.
While this might be a little confusing, let me explain each line and what is happening.
Lines 2-4 - This is just a bypass so that the sysop does not have to login and bypasses the filter in this case. Mostly for a performance reason
Lines 5-7 - This is a type of "backdoor" that I like to add to these types of filters because they are easy to lock yourself out of the site. To use something like this, just issue a URL like this to get into the sysop control panel
http://your_site.com/?59@@!backdoor=42
The key=value pairs after the ! are turned into the properties of the form object in WCTL such that you can access the value by just using the dot notation of the key as in line 5. Line 6 looks up the sysop and line 7 clears all the data in the response buffer and then returns the ID of the sysop that was previously found. The clearoutput is important because these filters only allow certain data to be returned and extra data in the buffer will cause the filter to fail.
Lines 8 - 23, this is where the main work is done. It starts on line 8 where it checks to see if this new request is already known to WebCrossing or not. IF they are, then this is bypassed completely and the filter just returns nothing and allows normal processing to occur, which is to show the next page to the logged in person. This eliminates all the checking on each request for performance reasons.
Line 10 finally gets the cookie called "auth_email" that was presumably set by the main server. It then uses that information to lookup the user in the database, assuming that your users are stored by using their email address as their "username".
If the user is NOT found, then the process of creating a new user with these credentials is executed on line 15. This automatically keeps your WebCrossing database in synch with the external authentication system on your main server. New users are created automatically.
Lines 16-21 are just ways of setting the new user's personal information at the time of creation. Any type of user property can be created and added at this time.
Lines 22 - Finally this new user's ID is returned which essentially logs them into the WebCrossing site.
If none of the returns happen and we fall through to line 25, then it means that the user has no cookie or we could not find them in the user database. Therefore we must redirect them back to the main web server to login there and get a cookie set. It is then the job of the main server to present a form for login credentials, set a cookie, and then send them back to the WebCrossing server.
Whew! All that just to login with a simple cookie. There are some obvious security holes with this method, but they are easily fixed through the use of encrypted cookies.
We will discuss more options of authentication filters in subsequent posts.
Dave Jones
dave@lockersoft.com
http://www.youtube.com/lockersoft
Times have changed and cookies are no longer the evil baked goods of old. Cookies are one of the ways that web servers can track the stateless HTTP requests across multiple pages. WebCrossing can utilize this information that might be passed from a main web server like this graphic illustrates:


Visitor comes to site, main web server sets cookie, visitor navigates to WebCrossing forums, reads cookie, sends cookie information to main web server and gets validation in return.
This is a very common method of authentication and it is performed in the authentication filter like so. We will begin with just getting the cookie in the first place.
1 %% macro authenticateFilter %%
2 %% if userIsSysop %%
3 %% return %%
4 %% endif %%
5 %% if form.backdoor == "42" %%
6 %% set id userLookup("sysop") %%
7 %% clearoutput%%%% id %%%% return %%
8 %% else if userIsUnknown || userIsGuest %%
9 %%// Get Cookie and use it to lookup or create a user and then log them in %%
10 %% set _email envirCookie( "auth_email" ).fromURL %%
11 %% set id userLookup( _email ) %%
12 %% if id %%
13 %% clearoutput %%%% id %%%%return%%
14 %% else %%
15 %% set id userCreate( _personid ) //create the user %%
16 %% if id %%
17 %% set password randomString %%
18 %% id.setUserPassword(password) %%
19 %% id.setUserEmail(_email) %%
20 %% id.setUserMailbox(_email) %%
21 %% id.setUserForwardTo(_email) %%
22 %% clearoutput %%%% id %%%%return%%
23 %% endif // id %%
24 %% endif %%
25 %% clearoutput %%
26 HTTP/1.0 302 Redirect%% crlf %%
27 Location: http://your_main_site.com/login %% crlf %%%% crlf %%
28 %% endif // userIsUnknown || userIsGuest %%
29 %% endmacro %%
While this might be a little confusing, let me explain each line and what is happening.
Lines 2-4 - This is just a bypass so that the sysop does not have to login and bypasses the filter in this case. Mostly for a performance reason
Lines 5-7 - This is a type of "backdoor" that I like to add to these types of filters because they are easy to lock yourself out of the site. To use something like this, just issue a URL like this to get into the sysop control panel
http://your_site.com/?59@@!backdoor=42
The key=value pairs after the ! are turned into the properties of the form object in WCTL such that you can access the value by just using the dot notation of the key as in line 5. Line 6 looks up the sysop and line 7 clears all the data in the response buffer and then returns the ID of the sysop that was previously found. The clearoutput is important because these filters only allow certain data to be returned and extra data in the buffer will cause the filter to fail.
Lines 8 - 23, this is where the main work is done. It starts on line 8 where it checks to see if this new request is already known to WebCrossing or not. IF they are, then this is bypassed completely and the filter just returns nothing and allows normal processing to occur, which is to show the next page to the logged in person. This eliminates all the checking on each request for performance reasons.
Line 10 finally gets the cookie called "auth_email" that was presumably set by the main server. It then uses that information to lookup the user in the database, assuming that your users are stored by using their email address as their "username".
If the user is NOT found, then the process of creating a new user with these credentials is executed on line 15. This automatically keeps your WebCrossing database in synch with the external authentication system on your main server. New users are created automatically.
Lines 16-21 are just ways of setting the new user's personal information at the time of creation. Any type of user property can be created and added at this time.
Lines 22 - Finally this new user's ID is returned which essentially logs them into the WebCrossing site.
If none of the returns happen and we fall through to line 25, then it means that the user has no cookie or we could not find them in the user database. Therefore we must redirect them back to the main web server to login there and get a cookie set. It is then the job of the main server to present a form for login credentials, set a cookie, and then send them back to the WebCrossing server.
Whew! All that just to login with a simple cookie. There are some obvious security holes with this method, but they are easily fixed through the use of encrypted cookies.
We will discuss more options of authentication filters in subsequent posts.
Dave Jones
dave@lockersoft.com
http://www.youtube.com/lockersoft
Subscribe to:
Posts (Atom)