2.
Main processing flow goes in controller with some code parts borrowed from showcase example's 'ProviderSignInController':
Code:
@Controller
@RequestMapping("/canvas")
public class CanvasProviderSignInController {
private SignedRequestDecoder decoder;
@Autowired
private final SocialAuthenticationServiceLocator socialAuthenticationServiceLocator;
private final UsersConnectionRepository usersConnectionRepository; // mongodb repository class
private final ConnectSupport webSupport = new ConnectSupport();
@Autowired
private FacebookServiceProvider facebookServiceProvider;
private Facebook facebook;
private AccountService accountService; // custom helper mongodb service class
........... // other stuff
@Inject
public CanvasProviderSignInController(
SocialAuthenticationServiceLocator connectionFactoryLocator,
FacebookServiceProvider facebookServiceProvider,
UsersConnectionRepository usersConnectionRepository,
SignInAdapter signInAdapter,
SignedRequestDecoder decoder,
Facebook facebook,
AccountService accountService
) {
........... //constructor called from custom 'SocialAndSecurityConfig' configuration component class
......
@RequestMapping(value = "/", method = RequestMethod.GET)
public String processAccessToCanvas(NativeWebRequest request, Model model) {
log.error("Error accessing canvas FB app. 'signed_request' can't be found in GET access. Redirecting to " + baseErrorPage);
........ // USER CAN'T use app by GET url
}
@RequestMapping(value="/", method=RequestMethod.GET, params={"install"})
public String installApplication(
@RequestParam String install, Model model, NativeWebRequest request) {
.......... // I have my own 'canvas app install link' and I show it on 'error' page
OAuth2ConnectionFactory<?> connectionFactory = (OAuth2ConnectionFactory<?>)
socialAuthenticationServiceLocator.getConnectionFactory("facebook");
String authorizationUrl = buildFacebookRequestUrl(request, connectionFactory);
model.addAttribute("authorizationUrl", authorizationUrl);
return "/facebook/signin"; // page with inlined JS script to go to 'install FB app' page
}
@RequestMapping(value="/", method=RequestMethod.GET, params={"error_reason"})
public String canceledAuthorization(
@RequestParam String error_reason, Model model, NativeWebRequest request) {
log.error("Cancelled installing canvas FB app. 'signed_request' value is not found in GET access. Redirecting to " + baseErrorPage);
// user cancelled usign fb app, offer him to install app again by providing link on page
..........
// url for installing FB canvas app by inline JS code on target HTML page
model.addAttribute("facebookInstallAppUrl", canvasAppInstallUrl);
return baseErrorPage;
}
private String buildFacebookRequestUrl(NativeWebRequest request, OAuth2ConnectionFactory<?> connectionFactory) {
String authorizationUrl = webSupport.buildOAuthUrl(connectionFactory, request);
String sessionId = request.getSessionId();
if (sessionId != null && !sessionId.isEmpty()) {
authorizationUrl += "&state=" + sessionId;
}
authorizationUrl += "&scope=email"; // all necessary FB app permissions go here
return authorizationUrl;
}
/// The main working method goes here:
@RequestMapping(value = "/", method = RequestMethod.POST)
public String processFaceBookCanvasRequest(
NativeWebRequest request,
Model model) {
// get 'signed_request' parameters POST-ed by FB
String signedRequest = request.getParameter("signed_request");
// create Spring Social connection factory for later use
OAuth2ConnectionFactory<?> connectionFactory = (OAuth2ConnectionFactory<?>)
socialAuthenticationServiceLocator.getConnectionFactory("facebook");
if (signedRequest != null && !signedRequest.isEmpty()) {
Map<String, ?> decoded = null;
try {
// try to decode 'signed_request'
decoded = decoder.decodeSignedRequest(signedRequest);
} catch (SignedRequestException e) {
..............
try {
// facebook returns 'basic' data first access time
Map<String, String> userMap = (Map<String, String>)decoded.get("user"); // that data is known every time
// that fb data exists ONLY when user installed fb canvas app in his FB profile
String providerUserId = (String)decoded.get("user_id");
String oauthTokenValue = (String)decoded.get("oauth_token");
Integer expiresTimeValue = (Integer)decoded.get("expires");
// If user has canvas app installed in his FB profile ?
if (providerUserId == null &&
oauthTokenValue == null
&& expiresTimeValue == null) {
// user DON'T HAVE canvas app installed in his FB profile
// new user, redirect to 'install FB app' page
String authorizationUrl = buildFacebookRequestUrl(request, connectionFactory);
model.addAttribute("authorizationUrl", authorizationUrl);
return "/facebook/signin"; // page with inlined JS script to go to 'install FB app' page
} else {
// user DO HAVE canvas app installed in his FB profile
// that should be registered user
// user can be 'temporary' which is created by Spring Social in UserConnection table (not fully registered)
// user can be 'fully registered' by our app and has 'completed User/Profile' stuff with UserConnection record related to him
ConnectionKey key = new ConnectionKey("facebook", providerUserId);
ConnectionRepository connectionRepository =
usersConnectionRepository.createConnectionRepository(providerUserId);
Connection<?> connection = null;
try {
connection = connectionRepository.getConnection(key);
} catch (NoSuchConnectionException e) {
// something is wrong, userConnection is not found - go to install FB app again
String authorizationUrl = buildFacebookRequestUrl(request, connectionFactory);
model.addAttribute("authorizationUrl", authorizationUrl);
return "/facebook/signin"; // page with inlined JS script to go to 'install FB app' page
}
// find 'temporary' user created by 'Spring Social' code internally (custom code)
User notExistedUser = accountService.findOneByProviderUserId(providerUserId, providerUserId);
if (notExistedUser != null) {
// this is really 'new' user, but he should finish registration first
facebook = facebookServiceProvider.getApi(oauthTokenValue);
FacebookProfile userProfile = facebook.userOperations().getUserProfile();
model.addAttribute("profile", userProfile);
handleSignIn(connection, request, model);
// redirect user to page for finishing registration
String resultView = "redirect:/facebook/finishRegistration";
return resultView + "?providerUserId=" + providerUserId........ // pass other params from FB profile
} else {
// let's find 'internal User' who has finished registration previously (custom code)
notExistedUser = accountService.findOneByUserConnectionValuesInSocialSet(
providerUserId, "facebook", providerUserId);
if (notExistedUser != null) {
// we have completely registred User
HttpServletRequest httpRequest =
(HttpServletRequest) request.getNativeRequest();
// make automatic login into system via Spring Security
AccountUtils.setLoginUserAccount(notExistedUser, httpRequest);
return "profile/profile"; // show his profile
} else {
// it's should not happen, but let's have error processing
model.addAttribute("error", "Canvas Facebook App user not found error");
return baseErrorPage;
}
} catch (Exception e) {
log.error("Exception while handling 'signed_request'/'authToken' value (" + e + "). Redirecting to " + baseErrorPage);
..................
}
log.error("'signed_request' value is not found (" + signedRequest + "). Redirecting to " + baseErrorPage);
model.addAttribute("error", "'signed_request' value POST-ed by Facebook is not found");
model.addAttribute("error_description", "Looks like you are trying to install Facebook App outside of Facebook site ?");
return baseErrorPage;
}