Embedding comments on private sites


(Julian Tescher) #1

Is it possible to have comments be public only when embedded? Or pass SSO information with the embed script?

Currently if you require login then embedded comments are empty boxes until the user has logged in. Probably because ‘X-Frame-Options’ is ‘SAMEORIGIN’ for the discourse login page.

Thoughts?


(Jeff Atwood) #2

What is the use case for this? Can you provide a real world example of some kind? I am not following.


(Michelle Venetucci) #3

Sure, I can weigh in, it’s a project I’m using Discourse for and building with Julian.

We have a membership based site. We incorporated SSO with Discourse, and are using the forum as a commenting system as well. We made the forum private, since it’s a membership based site and only members can see content. But we’re running into a couple of snags, the biggest of which is:

When a user comes into our site, before they’ve ever clicked on the forum, they can’t see the embedded comments. So until they opt to click on “forum” in our navigation, they won’t see any comments on the site. We were hoping that users would see all the comments and the “continue the discussion” button as an incentive to participate. Since they can’t see the comments, there’s no prompt or incentive to participate; this greatly inhibits our ability to use the forum.

Looking for any feedback/help! Thanks!


(Jeff Atwood) #4

Hmm not sure. @eviltrout any thoughts on this? Perhaps embedding doesn’t respect the login stuff?


(Julian Tescher) #5

Another option may be to have embedded comments with SSO work the same way that Disqus handles it?

They include the SSO information with their embeds: Integrating Single Sign-On · Disqus

var disqus_config = function () {
  // The generated payload which authenticates users with Disqus
  this.page.remote_auth_s3 = '<message> <hmac> <timestamp>';
  this.page.api_key = 'public_api_key';
}

This could also solve the problem of users having to manually log in to Discourse because their account could be created when a comment thread is viewed.


(Michelle Venetucci) #6

Yes, that was our other issue: while SSO is technically working (and thank you for developing that!), when users come to the site, until they actually click into the forum they don’t have a profile in the forum (and are not seeing any comments). The extra step (they see a “log in” page the first time they click into the forum) is a bit inhibitive and clunky for us. A clean SSO would hopefully just create all these connections when they sign in to OUR site - no extra sign in page in the forum, ability to see comments, etc.


(Robin Ward) #7

I am not sure how we could show the comments on a private site if the user has never activated their account on Discourse. @sam Is there any way to have SSO automatically activate? Or will they always require the login step on our site?

If it can’t be done automatically I’m not sure how to enable this for embedded sites.


(Vikhyat Korrapati) #8

One way to do this would be to use a custom CurrentUserProvider. What we do is create an extra cross-domain auth_token cookie when a user logs into our site which is used by Discourse to create user accounts.

See: discourse-hummingbird/hummingbird_current_user_provider.rb at master · vikhyat/discourse-hummingbird · GitHub


(Michelle Venetucci) #9

Interesting, thanks - does discourse support this, or is this a workaround you created?


(Vikhyat Korrapati) #10

It was the standard way of doing login integration before SSO support was added to core, see:


(Julian Tescher) #11

What about having an option when requiring login to not require login on embedded comments? Currently the combination of embedded comments and requiring login is pretty broken. Maybe at least have a warning in the settings if you turn both on?

Seems like having embedded comments never require login could be a good compromise?


(Kane York) #12

But then your forum would be effectively public to anyone who could guess the embedding parameters, which would obviate the purpose of requiring login to read posts.

An obscure URL that lets you bypass security is insecure.


(Julian Tescher) #13

Yeah you’re right it looks like X-Frame-Options is set to ALLOWALL.

What about if it instead set X-Frame-Options: ALLOW-FROM with the embeddable host setting?


(Kane York) #14

No, it’s not about the frame options. It’s about the data being returned at all.

You cannot rely on the browser to provide security from data disclosure to the user - if the server returns the data, the user can request it.


(Julian Tescher) #15

That’s fair. What about the solution above similar to Disqus. Sending auth params with the embed request?


(Michelle Venetucci) #16

Anyone have suggestions or feedback on the Disqus solution? We’re really trying over here… appreciate anything!


(Michael Pavey) #17

Did this get resolved? I’m looking into adopting Discourse for a similar use case. For users to have to sign in to a ‘forum’ section before they could see the embedded comments on other pages would be a definite non-starter.


#18

Checking in to see if there are any recent updates for this?


(Dean Peterson) #19

I have gotten this to work with some help from existing plugins and some custom code. When a new user registers and signs in, if the user does not have a discourse account, I redirect them to the discourse subdomain. I require login, so discourse immediately redirects back to my service:

@GET
	@Produces(MediaType.APPLICATION_JSON)
	public Response sso(@Context HttpServletRequest req, @Context HttpHeaders headers) 
	{
		
		Map<String, String> responseObj = new HashMap<String, String>();
		req.getParameterNames();
		String payload = req.getParameter("sso");
		String sig = req.getParameter("sig");
		String key = "XXXXXXXXX";
		
		if (payload == null || sig == null) {
	        return Response.serverError().build();
	    }
	    try {
			if (!CryptoSecurity.checksum(key, payload).equals(sig)) {
				
				
				responseObj.put("checsum_failed",
							"checksum failed");
				return Response.status(Response.Status.BAD_REQUEST).entity(
						responseObj).build();

			}
			
			String urlDecode = URLDecoder.decode(payload, "UTF-8");
			String nonce = new String(Base64.decode(urlDecode));
			ssoService.setUrlDecode(urlDecode);
			ssoService.setNonce(nonce);
			
		    URI location = new URI(System.getProperty("trade_client_base") +"#/discourse");
		    return Response.temporaryRedirect(location).build();
		} catch (Exception e) {
			
			e.printStackTrace();
			responseObj.put("Discourse sso failed",
					e.getMessage());
			return Response.status(Response.Status.BAD_REQUEST).entity(
				responseObj).build();
		} 
	    
	 
	}
	
	@GET
	@Path("/complete")
	@Produces(MediaType.TEXT_PLAIN)
	@PermitAll
	public String completeSSO(@Context HttpServletRequest req, @Context HttpHeaders headers) 
	{
		KeycloakSecurityContext session = (KeycloakSecurityContext) req
				.getAttribute(KeycloakSecurityContext.class.getName());
		String key = "filthy_rich";
		String subject = session.getToken().getSubject();
		String name = session.getToken().getName();
		String username = session.getToken().getPreferredUsername();
		String email = session.getToken().getEmail();
		String avatar = null;
		List<UserProfileImageReference> files = imageRepository.getFileReferences(subject);
		if(files != null && files.size() > 0)
		{
			UserProfileImageReference reference = files.get(0);
			avatar = System.getProperty("baseserviceurl") + System.getProperty("userservices-rs") + "/file/image/"+reference.getMediumImageId();
		}
		
		Map<String, String> responseObj = new HashMap<String, String>();
		String urlEncode = null;
		try {
			urlEncode = ssoService.getNonce()
			        + "&name=" + URLEncoder.encode(name, "UTF-8")
			        + "&username=" + URLEncoder.encode(username, "UTF-8")
			        + "&email=" + URLEncoder.encode(email, "UTF-8")
			        + "&external_id=" + URLEncoder.encode(subject, "UTF-8");
			if(avatar != null)
			{
				urlEncode += "&avatar_force_update=1";
				if(avatar.startsWith("http://localhost")){
					avatar = "http://www.hollywoodreporter.com/sites/default/files/imagecache/675x380/2014/09/too_good_for_grumpy_cat.jpg";
				}
				urlEncode += "&avatar_url=" + URLEncoder.encode(avatar, "UTF-8");
			}
		
		} catch (UnsupportedEncodingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	    String urlBase64 = null;
		try {
			urlBase64 = new String(Base64.encode(urlEncode.getBytes("UTF-8")));
		} catch (UnsupportedEncodingException e1) {
			
			e1.printStackTrace();
			responseObj.put("Discourse sso failed",
					e1.getMessage());
//			return Response.status(Response.Status.BAD_REQUEST).entity(
//				responseObj).build();
		}
	    int length = 0;
	    int maxLength = urlBase64.length();
	    final int STEP = 60;
	    String urlBase64Encode = "";
	    while (length < maxLength) {
	        urlBase64Encode += urlBase64.substring(length, length + STEP < maxLength ? length + STEP : maxLength) + "\n";
	        length += STEP;
	    }
	    
	    String location = null;
	    try {
	    	location = "http://discourse.abecorn.com/session/sso_login?sso=" + URLEncoder.encode(urlBase64Encode, "UTF-8") + "&sig=" + CryptoSecurity.checksum(key, urlBase64Encode);
		
			//location = new URI("http://discourse.abecorn.com/session/sso_login?sso=" + URLEncoder.encode(urlBase64Encode, "UTF-8") + "&sig=" + CryptoSecurity.checksum(key, urlBase64Encode));
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			responseObj.put("Discourse sso failed",
					e.getMessage());
//			return Response.status(Response.Status.BAD_REQUEST).entity(
//				responseObj).build();
		} 
	    return location;//Response.temporaryRedirect(location).build();
	    
	 
	}

When the entire process completes I have this plugin installed, to redirect users from discourse back to my site: GitHub - FutureProofGames/discourse_sso_redirect: A tiny plugin to provide a whitelist for external SSO redirects.