SSO take 2

Ok so we purchased a new template from Theme Forest this week for our “ASM” system. So while I was reskinning the login box I started having issues outputting the html dynamically using jquery (the old box as you can see was rather simple). So I decided to iframe the entire modal window of the login box. The reason I ended up not doing this last time was because redirecting once authenticated was a bit of pain for some reason. But alas I have conquered that beast.

So what you end up with is something like this:

This is the page which needs to display a login box

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!--
 To change this template, choose Tools | Templates
 and open the template in the editor.
-->
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>FDLE:AppHub</title>
    <script src="/AppHub/login/script"></script>
    <script type="text/javascript" src="/AppHub/js/jquery-1.3.2.min.js"></script>
    <script type="text/javascript" src="/AppHub/js/jquery-ui-1.7.2.custom.min.js"></script>
    <script>
        loadIFrameModal('local', window.location.protocol+"//"+window.location.host+"/AppHub/login/show/");
    </script>
</head>
<body>

</body>
</html>

The javascript (which is a GSP page)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function loadIFrameModal(env, targetUrl) {
    $(window).load(function() {
        var flag = ${flag};
        if(flag == false) {
            if(env == 'dev' || env == 'development') {
                $("body").append("<div id=\"modal\"></div>");
                $("#modal").append("<iframe src=\"http://162.143.99.200:8080/AppHub/login/?targetUrl="+targetUrl+"\" height=\"500\" width=\"400\" frameborder=\"0\" />");
            }
   
            if(env == 'local') {
                $("body").append("<div id=\"modal\"></div>");
                $("#modal").append("<iframe src=\"http://66869-irm:8080/AppHub/login/?targetUrl="+targetUrl+"\" height=\"475\" width=\"400\" frameborder=\"0\" />");
            }
   
            //open dialog
            $("#modal").dialog({
                autoOpen: true,
                height: 525,
                width: 475,
                modal: false,
                resizable: false,
                position: 'center',
                draggable: false,
                closeOnEscape: false
            });
            //hide the title bar
            $(".ui-dialog-titlebar").hide();
            $("#modal > iframe").attr("style", "overflow:hidden;");
        } else {
          $("body").append("<form id=\"form\" method=\"POST\" action=\""+targetUrl+"\"></form>");
          $("#form").append("<input type=\"hidden\" name=\"id\" value=\"${id}\" />");
          $("#form").append("<input type=\"hidden\" name=\"ip\" value=\"${request.getRemoteAddr()}\" />");
          $("#form").submit();
        }
    });
}

The iframe content:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
<body>
    <!-- Start Wrap -->
    <div id="wrap" class="login">
        <div id="content">
            <div class="inner">
       
                <noscript>
                    <!-- Show message when javascript is not enabled -->
                    <div class="message error">
                            <p>Javascript is needed for this template to work properly. <br />Please <a href="http://browsehappy.com/" title="Upgrade to a better browser">upgrade</a> your browser or <a href="http://www.google.com/support/bin/answer.py?answer=23852" title="Enable Javascript in your browser">enable</a> Javascript to navigate the interface properly.</p>
                        </div> 
                </noscript>
                <!-- Start Content Box #1 -->
                <div class="title">
           
                    <h3>fdle:Connect</h3>
           
                    <div class="selector" title="content_box">
                        <a class="tab active" href="tab-login">Login</a>
                        <a class="tab" href="tab-password">Forgotten Password</a>
                    </div>
                </div>
                <div class="box">
                    <div class="txt">
                   
                        <div id="content_box">
                            <div class="tab-login">

                                <!-- Start Message -->
                                <g:eachError bean="${loginCmd}">
                                    <div class="message error">
                                        <p><g:message error="${it}" /></p>
                                    </div>
                                </g:eachError> 
                                <!-- End Message -->
                           
                                <!-- Start Login Form -->
                                <g:form action="signIn">
                                    <fieldset>
                                        <p><label>Username</label>
                                        <input type="text" class="txt-input large" name="username" />
                                        </p>
                                        <p><label>Password</label>
                                        <input type="password" class="txt-input large" name="password" />
                                        </p>
                                        <p style="float:left;"><input type="submit" class="button" name="submit" value="Login" />
                                        <input type="reset" class="reset" name="reset" value="Reset" /></p>
                                        <input type="hidden" name="targetUrl" value="${targetUrl}" />
                                        <input type="hidden" name="ip" value="${request.getRemoteAddr()}" />
                                    </fieldset>
                                </g:form>
                                <!-- End Login Form -->
                           
                            </div>
                            <div class="tab-password">
                                <!-- Start Password Form -->
                                <form method="post" action="index.html">
                                    <fieldset>
                                        <p><label>Email</label>
                                        <input type="text" class="txt-input large" />
                                        <small>Please enter the email address of your account</small></p>

                                        <input type="submit" class="button" name="submit" value="Request Password" />
                                    </fieldset>
                               
                                </form>
                                <!-- End Password Form -->
                            </div>
                        </div>
                   
                    </div>
                </div>
                <!-- End Content Box #1 -->
           
            </div>
        </div>         
    </div>
    <!-- End Wrap -->
</body>

And the page that redirects once authenticated:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!--
 To change this template, choose Tools | Templates
 and open the template in the editor.
-->

<%@ page contentType="text/html;charset=UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>FDLE:Connect</title>
    <script src="http://www.google.com/jsapi"></script>
    <script>
      // Load jQuery
      google.load("jquery", "1.3.2");
      google.load("jqueryui", "1.7.2");
      google.setOnLoadCallback(function() {
            $(document).ready(function() {
                self.parent.location = '${targetUrl}';         
            });
      });
    </script>
  </head>
  <body>
    <span style="color:#000; font-size:14px; font-family:verdana;">If this page does not redirect please click the following <a href="${targetUrl}" target="_top">link</a>.</span
 </body>
</html>

Posted in: grails, javascript

Implementing Single Sign On…

So when I set out to rewrite our ASM system, I knew I had to find some selling points to the architecture I was pitching. One of them was the ability to write an SSO system that was easy and efficient and most of all did not require much code for implementing applications (JSSO and the like were out of the question).

So here’s what I came up with (everything is written in Grails)… I first created an AuthController which would handle the authentication and return back a user id for the person if they were a valid user (either in our custom user store or our AD system). This controller had a login action which displayed the login prompt and would return all the necessary error back to the user if something was wrong.

I then wrote another action whose corresponding GSP stored my javascript for creating the needed iframes on the page. This meant that when someone loaded the action I could access all of their cookies (which I needed for SSO) even if their application was on another domain. In the action I would process the cookies and either send them the login page or return the user id back to the browser which would then be processed by the application.

In the end the only code the implementing applications need to have is the follow:

1
2
3
4
5
6
7
8
9
10
11
12
  <script src="http://www.google.com/jsapi"></script>
  <script src="http://localhost:8080/AppHub/auth/script"></script>
  <script>
    // Load jQuery
    google.load("jquery", "1.3.2");
    google.load("jqueryui", "1.7.2");
    google.setOnLoadCallback(function() {
      loadModal('local',
      window.location.protocol+"//"+window.location.host+"/LoginApplication/login/show/",
      window.location.protocol+"//"+window.location.host+window.location.pathname);
    });
  </script>

And here is how I process the script action:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def script = {
    def cookie = request.cookies.find { it.name == 'session_id' }
    if (!cookie) {
      render(view: "script", contentType: "text/javascript", model: [flag: 'false'])
    } else {
      def id = Session.findBySessionIdAndKey(cookie.value, "id")
      if (id && cookie.value == id.sessionId) {
        render(view: "script", contentType: "text/javascript", model: [flag: 'true', id: id.value])
      } else {
        cookie.maxAge = 0
        response.addCookie(cookie)
        render(view: "script", contentType: "text/javascript", model: [flag: 'false'])
      }
    }
  }

And here is the script.gsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
function loadModal(env, targetUrl, returnUrl) {
  var flag = ${flag};
  $(window).load(function() {
    if(flag == false) {
      if(env == 'dev' || env == 'development') {
          $("head").append("<link rel=\"stylesheet\" href=\"http://162.143.99.200:8080/AppHub/css/ui-darkness/jquery-ui-1.7.2.custom.css\" type=\"text/css\" media=\"screen\" />");
          $("head").append("<link rel=\"stylesheet\" href=\"http://162.143.99.200:8080/AppHub/css/login.css\" type=\"text/css\" media=\"screen\" />");
          $("head").append("<link rel=\"stylesheet\" href=\"http://162.143.99.200:8080/AppHub/css/form.css\" type=\"text/css\" media=\"screen\" />");
          $("body").append("<div id=\"modal\"></div>");
          $("#modal").append("<div id=\"box\"></div>");
          $("#box").append("<h2 style=\"color:#666666;\">AppHub:<span class=\"light\">Connect</span></h2>");
          if(hasError('user.username.invalid')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.username.invalid" /></p>");
          }
          if(hasError('user.username.blank')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.username.blank" /></p>");
          }
          if(hasError('user.password.blank')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.password.blank" /></p>");
          }
          if(hasError('user.password.invalid')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.password.invalid" /></p>");
          }
          if(hasError('user.adproblem')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.adproblem" /></p>");
          }
          if(hasError('user.kerberos.6')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.kerberos.6" /></p>");
          }
          if(hasError('user.kerberos.18')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.kerberos.18" /></p>");
          }
          if(hasError('user.kerberos.23')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.kerberos.23" /></p>");
          }
          if(hasError('user.kerberos.24')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.kerberos.24" /></p>");
          }
          $("#box").append("<form id=\"form\" method=\"POST\" action=\"http://162.143.99.200:8080/AppHub/auth/signIn\"></form>");
          $("#form").append("<input type=\"hidden\" name=\"targetUrl\" value=\""+targetUrl+"\" />");
          $("#form").append("<input type=\"hidden\" name=\"returnUrl\" value=\""+returnUrl+"\" />");
          $("#form").append("<input type=\"hidden\" name=\"ip\" value=\"${request.getRemoteAddr()}\" />");
          $("#form").append("<ul></ul>");
          $("#form > ul").append("<li><label for=\"username\">Username</label><div><input type=\"text\" name=\"username\" class=\"max text\" /></div></li>");
          $("#form > ul").append("<li><label for=\"password\">Password</label><div><input type=\"password\" name=\"password\" class=\"max text\" /></div></li>");
          $("#form > ul").append("<li><input type=\"submit\" name=\"submit\" class=\"button\" value=\"Sign In\" /></li>");
          $("#form > ul").append("<li><a style=\"color:#0088CC;\" href=\"#\">Forgot Password</a><span style=\"color:#0088CC;\"> | </span><a style=\"color:#0088CC;\" href=\"#\">Register</a><span style=\"color:#0088CC;\"> | </span><a style=\"color:#0088CC;\" href=\"#\">Unlock Account</a></li>");
      }

      if(env == 'local') {
          $("head").append("<link rel=\"stylesheet\" href=\"http://localhost:8080/AppHub/css/ui-darkness/jquery-ui-1.7.2.custom.css\" type=\"text/css\" media=\"screen\" />");
          $("head").append("<link rel=\"stylesheet\" href=\"http://localhost:8080/AppHub/css/login.css\" type=\"text/css\" media=\"screen\" />");
          $("head").append("<link rel=\"stylesheet\" href=\"http://localhost:8080/AppHub/css/form.css\" type=\"text/css\" media=\"screen\" />");
          $("body").append("<div id=\"modal\"></div>");
          $("#modal").append("<div id=\"box\"></div>");
          $("#box").append("<h2 style=\"color:#666666;\">AppHub:<span class=\"light\">Connect</span></h2>")
          if(hasError('user.username.invalid')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.username.invalid" /></p>");
          }
          if(hasError('user.username.blank')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.username.blank" /></p>");
          }
          if(hasError('user.password.blank')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.password.blank" /></p>");
          }
          if(hasError('user.password.invalid')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.password.invalid" /></p>");
          }
          if(hasError('user.adproblem')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.adproblem" /></p>");
          }
          if(hasError('user.kerberos.6')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.kerberos.6" /></p>");
          }
          if(hasError('user.kerberos.18')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.kerberos.18" /></p>");
          }
          if(hasError('user.kerberos.23')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.kerberos.23" /></p>");
          }
          if(hasError('user.kerberos.24')) {
            $("#box").append("<p class=\"class\" style=\"color:#f00;\"><strong>Error:</strong><g:message code="user.kerberos.24" /></p>");
          }
          $("#box").append("<form id=\"form\" method=\"POST\" action=\"http://localhost:8080/AppHub/auth/signIn\"></form>");
          $("#form").append("<input type=\"hidden\" name=\"targetUrl\" value=\""+targetUrl+"\" />");
          $("#form").append("<input type=\"hidden\" name=\"returnUrl\" value=\""+returnUrl+"\" />");
          $("#form").append("<input type=\"hidden\" name=\"ip\" value=\"${request.getRemoteAddr()}\" />");
          $("#form").append("<ul></ul>");
          $("#form > ul").append("<li><label for=\"username\">Username</label><div><input type=\"text\" name=\"username\" class=\"max text\" /></div></li>");
          $("#form > ul").append("<li><label for=\"password\">Password</label><div><input type=\"password\" name=\"password\" class=\"max text\" /></div></li>");
          $("#form > ul").append("<li><input type=\"submit\" name=\"submit\" class=\"button\" value=\"Sign In\" /></li>");
          $("#form > ul").append("<li><a style=\"color:#0088CC;\" href=\"#\">Forgot Password</a><span style=\"color:#0088CC;\"> | </span><a style=\"color:#0088CC;\" href=\"#\">Register</a><span style=\"color:#0088CC;\"> | </span><a style=\"color:#0088CC;\" href=\"#\">Unlock Account</a></li>");
      }

      //open dialog
      $("#modal").dialog({
          autoOpen: true,
          height: 450,
          width: 500,
          modal: true,
          resizable: false,
          position: 'center',
          draggable: false,
          closeOnEscape: false
      });
      //hide the title bar
      $(".ui-dialog-titlebar").hide();
    } else {
      $("body").append("<form id=\"form\" method=\"POST\" action=\""+targetUrl+"\"></form>");
      $("#form").append("<input type=\"hidden\" name=\"id\" value=\"${id}\" />");
      $("#form").append("<input type=\"hidden\" name=\"ip\" value=\"${request.getRemoteAddr()}\" />");
      $("#form").submit();
    }
  });
}

function getParameters() {
    var vars = [], hash;
    var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
    for(var i = 0; i < hashes.length; i++) {
        hash = hashes[i].split('=');
        vars.push(hash[0]);
        vars[hash[0]] = hash[1];
    }
    return vars;
}

function getParameter(name) {
  return getParameters()[name];
}

function getErrors() {
  if(getParameter('error') != null) {
    return getParameter('error').split(';');
  } else {
    return null
  }
}

function hasError(code) {
  var errors = getErrors();
  if(getErrors() != null) {
    for(var i = 0; i < getErrors().length; i++) {
      if(errors[i] == code) {
        return true;
      }
    }
  } else {
    return false;
  }
}

Could it be cleaner? Sure and when I hone in my JQuery skills I will definitely clean it up, but for now it works. =)

Posted in: REST, grails, javascript

RESTClient 2.0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
import java.net.HttpURLConnection;

class RESTClient {
    String url
    String method = "GET"
    String body //I want the String representation of the object you want to send (either JSON or XML)
    String data //This is where the response data is stored
    Node xml //Store the processed response for xml in here
    Integer status //Where the status code is held, like 200, 201, 404, 400, etc...
    Map headers = ["Content-Type":"text/xml", "Accept":"text/xml"]
    Map params
    HttpURLConnection request
   
   
    def makeRemoteCall() {
        request = new URL(url).openConnection()
   
        //This is set to GET by default
        request.setRequestMethod(method)
   
        //Use the each closure to set all the requestHeader values
        headers.each { name, value ->
            request.setRequestProperty(name, value)
        }
       
        //Check to see what method you are performing, if post or put then you need to write out to the stream.
        if(method in ['POST', 'PUT']) {
            request.doOutput = true
            request.outputStream.withWriter("ASCII") { stream ->
                stream << body //Hopefully your body is well formatted xml or json =D
            }
        }
       
        request.connect()
        status = request.getResponseCode()
        if(status == 200 || status == 201) {
            if(method in ['GET', 'POST', 'PUT']) {
                data = request.content.text
                if(headers["Content-Type"] in ["text/xml", "application/xml"] && data) {
                    xml = new XmlParser().parseText(data)
                }
            }
        }
        return this
    }
   
}

Posted in: REST, groovy

Restful Client update

So I have just finished the first class of my rest client:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import groovy.xml.StreamingMarkupBuilder
import java.net.HttpURLConnection;

import apphub.util.RESTClient

/**
 * Created by IntelliJ IDEA.
 * User: Chauncey-JL
 * Date: Jan 13, 2010
 * Time: 11:59:39 AM
 * To change this template use File | Settings | File Templates.
 */

class AccessLevel {
    String appCode
    String accessTypeCode
    String accessLevelCode
    String displayName
   
    def static list(appId, accessTypeCode) {
        def accessLevels = []
        def client = new RESTClient(url:"http://localhost:8080/AppHub/accessLevel/${appId}/${accessTypeCode}").makeRemoteCall()
        if(client.status == HttpURLConnection.HTTP_OK) {
            client.xml.children().each { list ->
                def accessLevel = new AccessLevel()
                list.children().each { child ->
                    accessLevel.setProperty(child.name(), child.text())
                }
                accessLevels.add(accessLevel)
            }
        }
        return accessLevels
    }
   
    def static get(appId, accessTypeCode, accessLevelCode) {
        def client = new RESTClient(url:"http://localhost:8080/AppHub/accessLevel/${appId}/${accessTypeCode}/${accessLevelCode}").makeRemoteCall()
        if(client.status == HttpURLConnection.HTTP_OK) {
            def accessLevel = new AccessLevel()
            client.xml.children().each { child ->
                accessLevel.setProperty(child.name(), child.text())
            }
            return accessLevel
        }
    }
   
    def save() {
        def client = new RESTClient(url:"http://localhost:8080/AppHub/accessLevel", method:"POST", body:this.toXML().toString()).makeRemoteCall()
        if(client.status == HttpURLConnection.HTTP_CREATED) {
            return true
        } else {
            return false
        }
    }
   
    def update() {
        def client = new RESTClient(url:"http://localhost:8080/AppHub/accessLevel/${this.appCode}/${this.accessTypeCode}/${this.accessLevelCode}", method:"PUT", body:this.toXML().toString()).makeRemoteCall()
        if(client.status == HttpURLConnection.HTTP_OK) {
            return true
        } else {
            return false
        }
    }
   
   
    def delete() {
        def client = new RESTClient(url:"http://localhost:8080/AppHub/accessLevel/${this.appCode}/${this.accessTypeCode}/${this.accessLevelCode}", method:"DELETE", body:this.toXML().toString()).makeRemoteCall()
        if(client.status == HttpURLConnection.HTTP_OK) {
            return true
        } else {
            return false
        }
    }
   
    private toXML() {
        StreamingMarkupBuilder builder = new StreamingMarkupBuilder();
        def accessLevel = {
            mkp.xmlDeclaration()
            accessLevel {
                appCode(this.appCode)
                accessTypeCode(this.accessTypeCode)
                accessLevelCode(this.accessLevelCode)
                displayName(this.displayName)
            }
        }
        builder.bind(accessLevel)
    }
   
    def String toString() {
        this.toXML()
    }
   
}

And the test class:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package apphub.client

//def accessLevel = new AccessLevel(appCode:'LEGALDOC', accessTypeCode:'ACCESS_LVL', accessLevelCode:'TEST', displayName:'TEST')
//accessLevel.save()

AccessLevel.list('LEGALDOC', 'ACCESS_LVL').each {
    println "APPCODE:${it.appCode}"
}

println "AccessLevel.get().displayName:"+AccessLevel.get('LEGALDOC', 'ACCESS_LVL', 'USER').displayName

def lvl = new AccessLevel(appCode:'LEGALDOC', accessTypeCode:'ACCESS_LVL', accessLevelCode:'TEST', displayName:'TEST')
println "AccessLevel.save():${lvl.save()}"

lvl.displayName = 'Updated Display Name'
println "AccessLevel.update():${lvl.update()} --- Display Name:${lvl.displayName}"

Posted in: groovy, push

My own version of the RESTClient… Without all of the jars!

Ok here is the rough version I came up with this afternoon. It will definitely get better as I move deeper into my API.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package apphub.util;

import java.net.HttpURLConnection;

class RESTClient {
    String url
    String method = "GET"
    String body //I want the String representation of the object you want to send (either JSON or XML)
    String data //This is where the response data is stored
    Node xml //Store the processed response for xml in here
    Integer status //Where the status code is held, like 200, 201, 404, 400, etc...
    Map headers = ["Content-Type":"application/xml", "Accept":"text/xml"]
    Map params
    HttpURLConnection request
   
   
    def makeRemoteCall() {
        request = new URL(url).openConnection()
   
        //This is set to GET by default
        request.setRequestMethod(method)
   
        //Use the each closure to set all the requestHeader values
        headers.each { name, value ->
            request.setRequestProperty(name, value)
        }
       
        //Check to see what method you are performing, if post or put then you need to write out to the stream.
        if(method in ['POST', 'PUT']) {
            request.doOutput = true
            request.outputStream.withWriter("ASCII") { stream ->
                stream << body //Hopefully your body is well formatted xml or json =D
            }
        } else {
        }
       
        request.connect()
        data = request.content.text
        if(headers["Content-Type"] in ["text/xml", "application/xml"] && data) {
            xml = new XmlParser().parseText(data)
        }
        status = request.getResponseCode()
        return this
    }
   
}

And here is how I am using it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
    def static list(appId, accessTypeCode) {
        def accessLevels = []
        def client = new RESTClient(url:"http://localhost:8080/AppHub/accessLevel/${appId}/${accessTypeCode}").makeRemoteCall()
        if(client.status == HttpURLConnection.HTTP_OK) {
            client.xml.children().each { list ->
                def accessLevel = new AccessLevel()
                list.children().each { child ->
                    accessLevel.setProperty(child.name(), child.text())
                }
                accessLevels.add(accessLevel)
            }
        }
        return accessLevels
    }
   
    def static get(appId, accessTypeCode, accessLevelCode) {
        def client = new RESTClient(url:"http://localhost:8080/AppHub/accessLevel/${appId}/${accessTypeCode}/${accessLevelCode}").makeRemoteCall()
        if(client.status == HttpURLConnection.HTTP_OK) {
            def accessLevel = new AccessLevel()
            client.xml.children().each { child ->
                accessLevel.setProperty(child.name(), child.text())
            }
            return accessLevel
        }
    }

Pretty simple =D

Posted in: REST, groovy

Fun with Groovy and RESTClient

So I have finished up the basic API for our user management system in grails so it is now time to switch gears and start working on the client. I decided to go with groovy (hey I need to hone in my skills =P) so I went off to find out how other people were attaching onto RESTful apis. Well it seems that most people are using the RESTClient found in the HTTPBuilder library. It has WAY more dependencies than I would like to have but I will worry about that later. Here is the first consuming method I have written using the client.

1
2
3
4
5
6
7
8
9
10
11
12
13
    def static list(appId, accessTypeCode) {
        def accessLevels = []
        def client = new RESTClient("http://localhost:8080/AppHub/accessLevel/${appId}/${accessTypeCode}")
        def resp = client.get(contentType:ContentType.XML, query:[format:'xml'])
        resp.data.children().each { list ->
            def accessLevel = new AccessLevel()
            list.children().each { child ->
                accessLevel.setProperty(child.name, child.text())
            }
            accessLevels.add(accessLevel)
        }
        return accessLevels
    }
1
println AccessLevel.list('LEGALDOC', 'ACCESS_LVL').size()

Pretty straight forward and the code is much shorter than it would be in Java (or heck even in Groovy) if I needed to manually setup all of the HTTP/URL connection and parse the response back.

Notice that I am using some metaprogramming to dynamically build AccessLevel objects from the responding XML. This works for this particular example and will continue to work for most of the API. However, if I ever return a deeper xml tree (User -> List of some object -> object) It will take a bit more finesse.

Posted in: REST, groovy

Testing Restful grails controllers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
import grails.test.*
import grails.converters.XML;
import grails.converters.JSON;


class UserControllerTests extends GroovyTestCase {
   
    void testGETXMLResponse() {
        def controller = new UserController()
        def existingUser = new User(username: "fred", password: User.encrypt("letmein"), firstName: "Fred", lastName: "Flintstone", middleName: "T", phone: "555-555-5555", email: 'fred@flintstone.com', activationDate: new Date(), logonFailureCount: 0).save()
        controller.request.method = 'GET'
        controller.request.contentType = 'text/xml'
        controller.params.format = 'xml'
        controller.params.id = existingUser.id
        controller.show()
        def user = new XmlSlurper().parseText(controller.response.contentAsString)
        assert user.@id == existingUser.id //we have to use the .@id because id is stored as an attribute to the user tag
        assert user.phone.text() == existingUser.phone //we have to use phone.text() because it is stored as a value of the phone tag.
    }
   
    void testGETJSONResponse() {
        def controller = new UserController()
        def existingUser = new User(username: "wilma", password: User.encrypt("letmein"), firstName: "Wilma", lastName: "Flintstone", middleName: "T", phone: "555-555-5555", email: 'wilma@flintstone.com', activationDate: new Date(), logonFailureCount: 0).save()
        controller.request.method = 'GET'
        controller.request.contentType = 'text/json'
        controller.params.format = 'json'
        controller.params.id = existingUser.id
        controller.show()
        def user = JSON.parse(controller.response.contentAsString)
        assert user.id == existingUser.id //this is how we access json text
        assert user.phone == existingUser.phone
    }
   
    void testPUTXMLResponse() {
        def controller = new UserController()
        def existingUser = new User(username: "dino", password: User.encrypt("letmein"), firstName: "Dino", lastName: "Flintstone", middleName: "T", phone: "555-555-5555", email: 'dino@flintstone.com', activationDate: new Date(), logonFailureCount: 0).save()
        existingUser.phone = '555-555-1111'
        def xml = existingUser as XML
        controller.request.method = 'PUT'
        controller.request.contentType = 'text/xml'
        controller.params.format = 'xml'
        controller.request.content = xml.toString().getBytes()
        controller.request.getAttribute("org.codehaus.groovy.grails.WEB_REQUEST").informParameterCreationListeners()
        controller.params.id = existingUser.id
        controller.update()
        def user = XML.parse(controller.response.contentAsString)
        assert user.@id == existingUser.id
        assert user.phone.text() == existingUser.phone
    }
   
    // I have submitted a JIRA issue regarding this test not working.  http://jira.codehaus.org/browse/GRAILS-5585
    /*
     void testPUTJSONResponse() {
     def controller = new UserController()
     def pebbles = new User(username:"pebbles", password:User.encrypt("letmein"), firstName:"Pebbles", lastName:"Flintstone", middleName:"T", phone:"555-555-5555", email:'pebbles@flintstone.com', activationDate:new Date(), logonFailureCount:0, deactivationDate:null).save()
     def builder = new JSONBuilder()
     def result = builder.build { user = pebbles }
     controller.request.method = 'PUT'
     controller.request.contentType = 'application/json'
     controller.params.format = 'json'
     controller.request.content = result.toString().getBytes()
     controller.request.getAttribute("org.codehaus.groovy.grails.WEB_REQUEST").informParameterCreationListeners()
     controller.params.id = '4'
     controller.update()
     def user = JSON.parse(controller.response.contentAsString)
     assert user.id == 4
     assert user.phone == '555-555-1111'
     }
     */

   
    void testPOSTXMLResponse() {
        def controller = new UserController()
        def existingUser = new User(username: "barney", password: User.encrypt("letmein"), firstName: "Barney", lastName: "Rubble", middleName: "T", phone: "555-555-5555", email: 'barney@rubble.com', activationDate: new Date(), logonFailureCount: 0)
        def xml = existingUser as XML
        controller.request.method = 'POST'
        controller.request.contentType = 'text/xml'
        controller.params.format = 'xml'
        controller.request.content = xml.toString().getBytes()
        controller.request.getAttribute("org.codehaus.groovy.grails.WEB_REQUEST").informParameterCreationListeners()
        controller.create()
        def user = XML.parse(controller.response.contentAsString)
        assert user.username.text() == "barney"
    }
   
    // I believe this is not working due to the same bug for the PUT test. http://jira.codehaus.org/browse/GRAILS-5585
    /*
     void testPOSTJSONResponse() {
     def controller = new UserController()
     controller.request.method = 'POST'
     controller.request.contentType = 'text/json'
     controller.params.format = 'json'
     controller.request.content = '{"activationDate":"2009-12-09T15:28:37Z","activeDirectoryUsername":null,"class":"User","createdBy":null,"deactivationDate":null,"disabled":false,"email":"betty@rubble.com","firstName":"Betty","id":1,"lastAccessDate":null,"lastName":"Rubble","lastUpdatedBy":null,"lastUpdatedDate":null,"logonFailureCount":0,"middleName":"T","mustChangePassword":false,"password":"FtPJCHN","phone":"555-555-5555","useActiveDirectory":false,"username":"betty","version":null}'.getBytes()
     controller.create()
     def user = JSON.parse(controller.response.contentAsString)
     println controller.response.contentAsString
     assert user.id == 5
     assert user.username == "betty"
     }
     */

   
    void testDELETEResponse() {
        def controller = new UserController()
        def existingUser = new User(username: "fred", password: User.encrypt("letmein"), firstName: "Fred", lastName: "Flintstone", middleName: "T", phone: "555-555-5555", email: 'fred@flintstone.com', activationDate: new Date(), logonFailureCount: 0).save()
        controller.request.method = 'DELETE'
        controller.params.id = existingUser.id
        controller.delete()
        assert controller.response.status == 200
    }
   
   
}

Posted in: REST, grails, groovy

Testing Rest services 2.0

So I have run into more problems testing my restful services. However, it seems to stem from the fact that unit tests do not provide all the necessary tools for testing the services properly. Below is my new integration tests.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
    void testGETXMLResponse() {
        def controller = new UserController()
        controller.request.method = 'GET'
        controller.request.contentType = 'text/xml'
        controller.params.format = 'xml'
        controller.params.id = '1'
       
        controller.show()
       
        def getXMLUser = XML.parse(controller.response.contentAsString)
        assert getXMLUser.user.id == 1
        assert getXMLUser.user.username == "fred"
        assert getXMLUser.user.password == User.encrypt("letmein") 
    }

Posted in: REST, grails, groovy

Unit Testing Controller based RESTful Services in Grails

Just a quick example on how you can test the various HTTP methods for your Grails controllers.

Controller:

1
2
3
4
5
6
7
8
9
10
11
12
def show = {
        def user = User.get(params.id)
        if(user) {
            withFormat {
                html user:user
                xml { render user as XML }
                json { render user as JSON }
            }
        } else {
            response.sendError 404
        }
    }

Unit Tests:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    void testXMLResponse() {
        mockDomain(User, [new User(id:1, username:"fred", password:User.encrypt("letmein"))])

        controller.request.method = 'GET'
        controller.request.contentType = 'text/xml'
        controller.request.format = 'xml'
        controller.params.id = '1'
       
        controller.show()

        def user = XML.parse(controller.response.getContentAsString())
        assert user.id == 1
        assert user.username == "fred"
        assert user.password == User.encrypt("letmein")
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    void testJSONResponse() {
        mockDomain(User, [new User(id:1, username:"fred", password:User.encrypt("letmein"))])
       
        controller.request.method = 'GET'
        controller.request.contentType = 'text/json'
        controller.request.format = 'json'
        controller.params.id = '1'
       
        controller.show()
       
        def user = JSON.parse(controller.response.getContentAsString())
        println controller.response.contentAsString
        assert user.id == 1
        assert user.username == "fred"
        assert user.password == User.encrypt("letmein")
    }

Pretty simple!

Posted in: REST, grails, groovy

What am I?

I hate telling people I am a programmer…  I do so much more than program. I am an architect, a database administrator, a system administrator, a quality control expert, a customer support representative, and THEN I am a programmer. Heck I am probably more than just those things I listed – but you get the point… Why do people insist on calling us programmers? Why am I not called a Software ENGINEER!!!!

Posted in: software engineer