ServletContext or HttpRequest and Response in the Custom Login Module

Hi
I'm writting custom Login module for TeamCity. I'm trying to use SSO with one of our authentication provider, but I've noticed that I cannot get the HttpServletRequest or Response.
I need the request to check the SSO token.
How do I go about it?
Greg

0
26 comments

Hi Greg,

What TeamCity version do you target with your plugin?

Since TeamCity 7.1, there is a closed API with which it's easier to write HTTP-based authentication. "Closed" here means it's not declared open API and can change int he future versions without extra notice.

TeamCity  8.0 will probably have more improvements in the area, might change that  closed API and (hopefully) will make it an open one.


The approach here is to use the authentication mechanism in addition to one of the standard ones (built-in authentication or another login module).



Attached you can find an example plugin which makes use of the closed API I referred to. It is compatible with TeamCity 7.1


BTW, is the targeted SSO system a common one or custom used in your organization only?


Attachment(s):
superuserProofOfConcept_v02.zip
0

Hi Yegor
Thanks for you reply and for the example. That is very helpful.

To aswer your questions:

    1. We are using TeamCity Enterprise 7.1.3 (might upgrade it as soon as new version comes out).
    2. We are integrating with Atlassian Crowd server and it's SSO capabilities (ideally would like SSO capability accross TeamCity, JIRA, Confluence etc.)


Is there anything for the TeamCity at the moment that would cooperate with Crowd that you aware of? Did someone tried this integration before?

Cheers, Greg

0

Hi Greg,

Thank you for the details.

I am not aware of any public TeamCity-Crowd integration, while we had customers looking forward to integraiton and even trying writing a plugin.
An issue on the topic is TW-9752.
If you come up with a working plugin and can make it public, that would be great!

0

I will let you know when it's ready :)
Greg

0

Hi Yegor

How do you actually integrate the plugin?
Is it via   <auth-type>    <login-module /> in the main-config.xml or there is something else I should do?
If via the <login-module>, how does the HttpAuthenticationScheme fits into the picture?

Greg

0

Greg,

> How do you actually integrate the plugin?
This depends on what specifically you need to achieve.

If you want to preserve ability for the users to login as they do, but add Crowd-based authorization in addition, the way to go is to add HttpAuthenticationScheme. For the HttpAuthenticationScheme to be active it does not need any user-level configuraiton in any of the settings files.
If you want to login users via Crowd on entering their credentials on TeamCity login form, you will also need a login module with configuration of it in the main-config.xml

The latter is also applicable if you want to disable TeamCIty login form altogether and always redirect to Crowd.

BTW, we are doing improvements in the related API and user experience when switching authentication in TeamCity, so you might want to check our upcoming TeamCity 8.0 EAP build. We would be glad to receive feedback on the changes and improve things in the final 8.0 release (Q2 2013).

0

Hi Yegor

Thanks for your insightfull (as usual) answer.

What we would like to have is abillity of users to still use the TeamCity login page to login into TeamCity. However we would like for the authentication and authorization details to come from Crowd.
I assume I need both, HttpAuthenticationScheme and the Custom Login module.

I'm still puzzled a bit with the HttpAuthenticationScheme. How till TeamCity pick the Custom one over the Default that are in TeamCity?

I will use the 7 build for now as we will be live with this soon. However we will follow with the upgrades as they happen so the feedback will come to you soon after the new version is released. :)

Greg

0

Greg,

Each registered HttpAuthenticationScheme gets invoked for all not yet authenticated requests without respect of the current login module. Whether to handled request or not is to be desided by the logic of the code in HttpAuthenticationScheme.

TeamCity 8 EAP are not desiged to be used in production as they can be not stable. However, you can try the build on a test server or locally to see what is coming and provide early feedback until the functionality is baked into a release.

0

Hi Yegor
I'm going to need your help with the Closed API.

Following the example you attached to this discussion I've implemented the following: class CrowdAuthenticationScheme implements HttpAuthenticationScheme

The method:


    @Override
    public HttpAuthenticationResult processAuthenticationRequest(@NotNull HttpServletRequest httpServletRequest, @NotNull HttpServletResponse httpServletResponse) throws IOException {
        Maybe<User> possibleUser = crowdIntegrationClient.userAlreadyLoggedIntoCrowd(httpServletRequest, httpServletResponse);


        if (possibleUser.hasValue()){
            User user = possibleUser.getValue();
            log.info(String.format("Looks like user [%s] is already logged in.", user.getName()));
            return HttpAuthenticationResult.authenticated(new ServerPrincipal(REALM, user.getName()));
        }
        return HttpAuthenticationResult.notApplicable();
    }


I'm using Crowd API to check if the SSO token exists and user can be authenticated. If it does, I'm trying to return Authenticated result with username.

If I open new windows session and log into Crowd it sets the SSO token. When I navigate to TeamCity, the code above gives this exception:


[2013-01-17 14:26:49,478]  ERROR -   jetbrains.buildServer.SERVER - Error java.lang.IllegalStateException: Authentication scheme "teamcity.crowd.integration.CrowdAuthenticationScheme" changed response object and returned authentication result "AUTHENTICATED", while only "UNAUTHENTICATED" is allowed in this case while processing request: GET '/runtimeError.jsp', from client fe80:0:0:0:5166:1c86:35d:c27d:61459, no auth/user

java.lang.IllegalStateException: Authentication scheme "teamcity.crowd.integration.CrowdAuthenticationScheme" changed response object and returned authentication result "AUTHENTICATED", while only "UNAUTHENTICATED" is allowed in this case
        at jetbrains.buildServer.controllers.interceptors.auth.impl.HttpAuthenticationManagerImpl.doProcessAuthenticationRequest(HttpAuthenticationManagerImpl.java:7)
        at jetbrains.buildServer.controllers.interceptors.auth.impl.HttpAuthenticationManagerImpl.processAuthenticationRequest(HttpAuthenticationManagerImpl.java:18)
        at jetbrains.buildServer.controllers.interceptors.AuthorizationInterceptorImpl.preHandle(AuthorizationInterceptorImpl.java:31)
        at jetbrains.buildServer.controllers.interceptors.RequestInterceptors.preHandle(RequestInterceptors.java:3)
        at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:781)
        at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:719)
        at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:644)
        at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:549)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:621)
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:722)
        at jetbrains.buildServer.maintenance.TeamCityDispatcherServlet.service(TeamCityDispatcherServlet.java:16)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:305)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at jetbrains.buildServer.web.ParametersProviderCalculationContextFilter.doFilter(ParametersProviderCalculationContextFilter.java:10)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at jetbrains.buildServer.web.DisableSessionIdFromUrlFilter.doFilter(DisableSessionIdFromUrlFilter.java:1)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at jetbrains.buildServer.web.DiagnosticFilter.doFilter(DiagnosticFilter.java:51)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at jetbrains.buildServer.web.ResponseFragmentFilter.doFilter(ResponseFragmentFilter.java:0)
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
        at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:224)
        at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:169)
        at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:168)
        at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:98)
        at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:118)
        at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:407)
        at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:987)
        at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:539)
        at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:300)
        at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(Unknown Source)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
        at java.lang.Thread.run(Unknown Source)



It's hard to say what goes wrong when I cannot see what HttpAuthenticationManagerImpl is doing.
Could you help? What is the condition this Exception could ocure?

Greg

0

Greg,

The message
> changed response object and returned authentication result  "AUTHENTICATED", while only "UNAUTHENTICATED" is allowed in this case  while processing request:
most probably means that within a call to crowdIntegrationClient.userAlreadyLoggedIntoCrowd(httpServletRequest, httpServletResponse);
So, the method has writen something to the response and there is no way for TeamCity to proceed with normal request handling and response was already affected.

Does this make sense?

0

Hi Yegor
It does make sense. I couldn't find any documentation on what Crowd API was doing so I needed to debug. It is writting to response.
Thank you.

On another note.
I will use the HttpAuthenticationScheme to check for the Crowd SSO. Use the LoginModule to log into TeamCity if the SSO cookie is not set.
How would I go about setting the sso cookie on the session once I got log into the TeamCity, so I don't need to log into JIRA next?

Any ideas?

Greg

0

Greg,

> How would I go about setting the sso cookie on the session once I got  log into the TeamCity, so I don't need to log into JIRA next?

Usually an SSO cookie is set by the SSO service itself. Moreover, a service running on one domain cannot set a cookie for another server running on another domain.

So, for SSO to work users should probably be redirected to the SSO service to login or to figure out they are already logged in. A usual approach for the case is to add a link to the login form like "Login via Crowd" to do the redirect.

Answering your question, there is no easy way for a login module to modify a resoponse (which is necessary to add a cookie). This would be only possible by provding custom login page with own controller in the plugin and not the default one.

0

Hi Yegor

It doesn't look like Crowd offers standard login page that redirects back to application. Seems like Atlassian is forgeting some base functionality :(
Anyways, I will have to look at the Custom TC login page with controler. How do I approach this from a plugin perspective and integrate it?

I do appriciate all you help Yegor. You are ACE! :)

Greg

0

> Anyways, I will have to look at the Custom TC login page with controler. How do I approach this from a plugin perspective and integrate it?

You can define a new page (TeamCity will redirect to) via LoginPageProvider extension.
Hope this helps.

0

Hi Yegor
Will need to pull your leg a little bit more.

  1. Is it enough to implement LoginPageProvider and add it as a Bean or do I need to register it somewhere?
  2. getLoginPageUrl returns a String. I would like to point to a custom login page.
  3. Is it possible to build Custom Login Page with LoginController with SimplePageExtension or PageExtension?


I'm not sure how do I put those things together. Not a lot of docos and examples show how to extend current page. ?:|

Thanks for you help Yegor.

BTW. Server Profiling example of a Custom UI is pointing to a WebExtension which is Deprecated.

Greg

0

Hi Greg,
> 1. Is it enough to implement LoginPageProvider and add it as a Bean or do I need to register it somewhere?
Yes, it's enough to implement it and add as a bean.

> 2. getLoginPageUrl returns a String. I would like to point to a custom login page.

That should be an URL. You can use PluginDescriptor.getPluginResourcesPath(relativePath) where a controller is registered by relativePath.

> 3. Is it possible to build Custom Login Page with LoginController with SimplePageExtension or PageExtension?

For the custom login page approach, you will need to have a separate page (thus, no PageExtensions): e.g. extend BaseController and register it via WebControllerManager.registerController
However, you will need to implemnet the page from scratch.

Depending on the specific functionality you want to achieve, you migjht also find useful not to implement entire login page, but just add a link to the existing one, like NTLM authentication does. This is done via extension PlaceId.LOGIN_PAGE

You can describe the use cases of the corwd integration so that I can advice on the best approach possible in TeamCity.
Also, if you want I can look into the current plugin sources you have and demonstrate one or another approach with them.

0

Hi Yegor

The custom login page is only required to setup Crowd Authentication token, once user logs in. It will enable Crowd SSO functionality. Users in our organisation will only log into one of the Systems once (JIRA, Confluence, Crowd, TeamCity, Sonar) and be able to use all of them.

Ideally I would love to keep styling of a current TeamCity login page, just add authentication token in HttpResponseHandler once the login happens. Does it make sense?

Ps. I will make plugin and sources publicly available in the next day or two (once I got a bit of time) :)

Cheers, Greg

0

Greg,

Thank you for the details.

Could you please describe what should happen when a new (not yet authenticated user) opens a TeamCity page?

Here are several possibilities I see with the implementation comments:

1. With openID-like authentication  which is the only way to login it would be appropriate to redirect user's browser to the authentication site which will then redirect back to TeamCity with authentication data included. On this request, TeamCity can set authentication cookie and redirect to the original page requested by the user.

(a way to implement this in TeamCity would be HttpAuthenticationScheme which will do the first redirect and either the very same HttpAuthenticationScheme or a separate controller (via BaseController) for handling the second)

1a. If openID is provided in addition to the current authentication, a link "login via XXX" can be added to the login form wich will perform the same scenario as above on user clicking the link.

(implementaiton will be much alike the previous with HttpAuthenticationScheme handling only some requests and an extension to the login page via PlaceId.LOGIN_PAGE)

2. If the authentication does not use user browser redirect, but needs to ask user some authentication credentials in case of not yet authenticated user, it should display a custom page for the user to get the credentials and then set authentication cookie.

(this would require HttpAuthenticationScheme to determine if the authentication cookie is already in place and a custom page created by a custom controller (via BaseController) to ask for the additional data and handle the post-actions (by the same controller, one more controller or in the HttpAuthenticationScheme. Redirect to this custom page can be handled either in HttpAuthenticationScheme  (302 redirect or directly on the server by including another page into the response) or via defining of a custom login page (via LoginPageProvider and then handling the page with own custom controller)


If none of the ways match your case, could you please detail the behavior of the user annd the code during an authentication process?


As to you question:

> Ideally I would love to keep styling of a current TeamCity login page, just add authentication token in HttpResponseHandler once the login happens.

This approach probably means that on first page load user will be presented with the standard login page, enter credentials and only then gets authentikation token set. This seems to betray SSO idea...

Anyway, if you do need this behavior - there is no way to modify response on user login via standard login page + login module. The only approach here is to make your own login page which will handle all the standard page functionality and then modify the response while handling following authentication request with credentials specified.

0

Hi Yegor
Thanks for your replay and advise.

Looks like the functionality I'm after you described in scenario 2. Let me try to explain the  UX as I see it:

Scenarion1. User is not loged in to TeamCity and NOT loged in to any other application that uses Crowd (Crowd SSO token is not set)

  1. User navigates to TeamCity page (any).
  2. TC recognises not loged in user and redirects to Login Page
  3. User attempts to login with Crowd credentials
  4. TC checks credentials via Crowd REST Api
  5. User is authenticated and Crowd SSO Token is set in the Cookie
  6. User gets redirected to a page originaly requested.
  7. End


Scenarion2. User is logged in to application that uses Crowd, for example JIRA (Crowd SSO Token is set in a cookie).

  1. User navigates to any TC page
  2. TC recognises that user is not loged in.
  3. TC attempts to authenticate user via Crowd REST Api with the SSO Token (with the HttpAuthenticationScheme)
  4. User is authenticated and see the page originaly requested.


Does it make sense? I think it kind-a fits the scenarion 2 described by you.
Just need to takle the custom Login Page and Controller so when the user is loged in the Crowd SSO Token could be set.

Greg

0

Greg,

Thanks for the description.

The scenario seems to be possible only if all the services user needs to suthenticate in are running under the same domain (so that the cookie set by one service canbe read by another).
Hope this is the case.

The way to approach the logic you described is to add a custom page which will ask the credentials and set the cookie. Unfortunately, there is no way to reuse the standard login page, so it's probably better at this time to implement your own separate page.

0

Hi Greg. Did you get your Crowd auth plugin working? I would be interested in taking a look at it. Did not see it in the plugin repository.

cheers,

-j

0

Yes. I'm moving it to TC 8 now. I'll let you know once I got something worth trying.
Greg

0

Hi Greg.

I was curious if you have made any ground on this?

0

hi,Greg
     thanks for your plugins ;  It is useful,I can log in teamcity with  crowd user; but it can not realize Single sign on ;can you tall me what shuold I do ?:x

0

Hi Bob
The short answer is, SSO doesn't work with this plugin. Not yet. I never had the time to implement it.
Try the GitHub in the future for discussion on the plugin, as I can make Issues into features (and feature requests).
Greg

0

Please sign in to leave a comment.