Let’s take for example the Add Unique Tokens to HTTP Requests Using ESAPI article, which has the 7d647e95-e47f-42e3-bb84-fd0dd727245c GUID, and can be opened directly at https://teammentor.net/article/7d647e95-e47f-42e3-bb84-fd0dd727245c (free account is needed to see that link)
Here is what happens if we try to open that link without first being logged in (btw: next version of TM show a more friendly message in these cases):
Since there is quite a bit of Javascript when loading an /article/GUID link lets see what happens when we open an /content/GUID link (which just returns the article’s content), in this case the https://teammentor.net/content/7d647e95-e47f-42e3-bb84-fd0dd727245c page
Using Fiddler, we can see that what is holding the 'logged in status' is the Session Cookie value.
For example, this request (with a valid Session cookie value), will return the article content:
... and if we make the same request without the Session cookie, we will get a 302 redirect into the TeamMentor Login page:
This means that in order to get content from a live TeamMentor site we need to do two requests:
- login with a valid username and password and get a Session GUID
- make a request to get a specific article (using the session GUID in a cookie)
Using the O2’s REPL script environment, here is a simple script that gets a login token:
Web.Https.ignoreServerSslErrors(); // in case we have fiddler on var crendential = "TestAccounts.xml".credential("TeamMentor"); // load username/password from disk var loginRequest = "https://teammentor.net/rest/login/{0}/{1}" // create GET URL .format(crendential.UserName, crendential.Password); return loginRequest.GET(); // send request
This xml string can be easily parsed like this:
var crendential = "TestAccounts.xml".credential("TeamMentor"); // load username/password from disk var loginRequest = "https://teammentor.net/rest/login/{0}/{1}" // create GET URL .format(crendential.UserName, crendential.Password); return loginRequest.GET() // send request .xRoot() // parse XML and return root .innerXml(); // get innerXml of root element //.guid(); // transform string into a GUID object
… so that we have a GUID string:
Tip: while developing the script it is useful to create Lamdba methods to help out and to store variables in the o2Cache object
Func<string> getLoginGuid = ()=> loginRequest.GET() // send request .xRoot().innerXml(); // get guid value value var loginId = "loginId".o2Cache<string>(getLoginGuid); // get cache value (or call getLoginGuid) return loginId;
var loginId = "login_Id".o2Cache<string>(getLoginGuid); // get cache value (or call getLoginGuid) var tmArticleUrl = "https://teammentor.net/content/" + // REST GET call for content "7d647e95-e47f-42e3-bb84-fd0dd727245c"; // GUID of article to get var response = tmArticleUrl.GET("Session=" + loginId); // make GET request with provided cookie value return response;
Note that if a valid session ID is not provided the returned HTML will be the TM’s login page:
While on the C# REPL UI an easy way to view the HTML content is to show it on a Web Browser control:
var topPanel = panel.clear().add_Panel(); // clear the REPL panel host control var webBrowser = topPanel.add_WebBrowser() // add a Web Browser control .silent(true) // make it silent (don't show Javascript errors) .set_Html(response); // load article content into it
Tip: if you want to quickly apply some CSS, then wrap the article html code in an HTML body:
var response = tmArticleUrl.GET("Session=" + loginId); // make GET request with provided cookie value var htmlTemplate = @"<html> <head> <link href='http://getbootstrap.com/dist/css/bootstrap.css' rel='stylesheet'> </head> <body> {0} </body> </html>"; var html = htmlTemplate.format(response); // create html from template and response var topPanel = panel.clear().add_Panel(); // clear the REPL panel host control var webBrowser = topPanel.add_WebBrowser() // add a Web Browser control .silent(true) // make it silent (don't show Javascript errors) .set_Html(html); // load article content into it
… which will make the HTML shown look better:
Creating a stand alone tool to view Article’s content, raw data as xml and raw data as jsonp
Now that we have the capability to get content, let’s create a mini tool to view it.
Starting by wrapping the content fetch and css wrapping into its own methods:
Func<string,string,string,string> getArticle = (contentType, articleId, sessionId) =>{ var tmArticleUrl = "https://teammentor.net/{0}/{1}"; // REST GET call for content return tmArticleUrl.format(contentType,articleId) // set contentType and session .GET("Session=" + sessionId); // make GET request with provided cookie value }; Func<string,string> wrapCss = (bodyContent) =>{ return @"<html> <head> <link href='http://getbootstrap.com/dist/css/bootstrap.css' rel='stylesheet'> </head> <body> {0} </body> </html>".format(bodyContent); };
var topPanel = panel.clear().add_Panel(); // clear the REPL panel host control var htmlViewer = topPanel.title ("Article as Html" ).add_WebBrowser(); // use webBrowser to see article's content var rawViewer = topPanel.insert_Right("Article as Raw" ).add_WebBrowser(); // use webBrowser to see article's object as jsonp var jsonpViewer = topPanel.insert_Below("Article as Jsonp").add_WebBrowser(); // use webBrowser to see article's object as raw xml var userId = "user_Id".o2Cache<string>(getLoginGuid); // get session id from getLoginGuid() or cache var article = "7d647e95-e47f-42e3-bb84-fd0dd727245c"; var html = wrapCss(getArticle("content",article, userId)); var raw = getArticle("raw" ,article, userId); var jsonp = getArticle("jsonp" ,article, userId); htmlViewer .set_Html(html); rawViewer .set_Html(raw); jsonpViewer .set_Html(jsonp); return html;
- Top left: Article Html (with a little bit of css for mating)
- Bottom left: Article XML article as a Jsonp object
- Right: Article XML
Next lets refactor the code so that we have one Lamdba method that triggers the entire workflow (login, fetch and show data)
Func<string,string,string> getLoginId = (username, password) =>{ var loginRequest = "https://teammentor.net/rest/login/{0}/{1}" // create GET URL .format(username, password); return username.add(password) // use username+password as cache key .o2Cache<string>(()=>loginRequest.GET().xRoot().innerXml()); // login request made here }; Action<string,string,string> showArticle = (articleId, username, password) =>{ var userId = getLoginId(username, password); var html = wrapCss(getArticle("content",articleId, userId)); var raw = getArticle("raw" ,articleId, userId); var jsonp = getArticle("jsonp" ,articleId, userId); htmlViewer .set_Html(html); rawViewer .set_Html(raw); jsonpViewer .set_Html(jsonp); }; var crendential = "TestAccounts.xml".credential("TeamMentor"); // load username/password from disk var article = "7d647e95-e47f-42e3-bb84-fd0dd727245c"; // article to show showArticle(article, crendential.UserName, crendential.Password); // trigger show article workflow
… add an ‘Actions’ panel to the top with some textboxes (article, username, password) and link
var topPanel = panel.clear().add_Panel(); // clear the REPL panel host control Action loadData = null; // placeholder for this lamda method var configPanel = topPanel.insert_ActionPanel(); var username_TextBox = configPanel .add_Label ("Username:" ).top(2).append_TextBox(""); var password_TextBox = username_TextBox.append_Label("Password:" ).top(2).append_TextBox("").isPasswordField(); var article_TextBox = password_TextBox.append_Label("Article to Show").top(2).append_TextBox("").width(400); var loadData_Link = article_TextBox .append_Link ("Load Data" , () => loadData());
… and use these controls to set the default values and set the data
loadData = ()=> showArticle(article_TextBox.get_Text(), username_TextBox.get_Text(), password_TextBox.get_Text()); var crendential = "TestAccounts.xml".credential("TeamMentor"); // load username/password from disk username_TextBox.set_Text(crendential.UserName); //set default values on TextBoxes password_TextBox.set_Text(crendential.Password); article_TextBox.set_Text("7d647e95-e47f-42e3-bb84-fd0dd727245c"); loadData_Link.click(); // trigger show article workflow
At the moment this mini-tool looks like this:
Finally, now that we have the desired functionality (i.e. view an article’s data for a particular article Id under an specific user account), we can run the UI under a WinForms Form (vs the REPL’s panel) , by making this change:
//var topPanel = panel.clear().add_Panel(); // use when in developent var topPanel = "PoC - View TeamMentor Article (using GUID)".popupWindow(1200,500); // use when done
To help with deployment and use, we can also save this script:
… and package it as a stand-alone exe:
Which can now be used independently:
I added a check to see if the credentials value is set (which will not be when running the stand-alone script)
… entering a valid username+password and article ID, will trigger the login and data loading:
… which will look like this:
… we can also now open any Article (using its ID) that exists on this server, for example the 6c470029-5c62-4394-99a9-8990bc48b0a8 one (whose title is: Change Session IDs During Authentication )
Download link:
If you want to try this mini-tool, you can get the stand-alone exe from here: PoC - View TeamMentor Article (using GUID v1.0.exe
Source code
Here is the final source code of this script: https://gist.github.com/DinisCruz/6523494