Created
March 13, 2018 21:20
-
-
Save yan130/47eb8e5ad7784a62230a5a35c66db4d9 to your computer and use it in GitHub Desktop.
LDAPProvider for silhouette authenticate
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package utils.auth | |
import com.mohiva.play.silhouette.impl.providers._ | |
import com.mohiva.play.silhouette.api._ | |
import com.mohiva.play.silhouette.impl.exceptions.ProfileRetrievalException | |
import com.mohiva.play.silhouette.api.util.{ ExecutionContextProvider, ExtractableRequest, HTTPLayer } | |
import com.mohiva.play.silhouette.impl.exceptions.{ AccessDeniedException, UnexpectedResponseException } | |
import com.mohiva.play.silhouette.impl.providers.state.UserStateItemHandler | |
import com.unboundid.ldap.sdk._ | |
import com.unboundid.util.ssl.{JVMDefaultTrustManager, SSLUtil, TrustAllTrustManager} | |
import play.api.Logger | |
import play.api.mvc._ | |
import scala.concurrent.Future | |
import java.net.URI | |
import javax.net.ssl.SSLSocketFactory | |
import play.api.libs.json._ | |
import play.api.libs.ws.WSResponse | |
case class LDAPSettings( | |
hostname: String, | |
group: String, | |
port: Int, | |
baseDN: String, | |
userDN: String, | |
groupDN: String, | |
objectClass: String, | |
trustAllCertificates: Boolean | |
) | |
case class LDAPInfo( | |
code: String, | |
username: String, | |
password: String | |
) extends AuthInfo | |
class LDAPProfileParser | |
extends SocialProfileParser[SearchResultEntry, CommonSocialProfile, LDAPInfo] { | |
val ID = "LDAP" | |
/** | |
* Parses the social profile. | |
* | |
* @param json The content returned from the provider. | |
* @return The social profile from given result. | |
*/ | |
override def parse(searchEntry: SearchResultEntry, authInfo: LDAPInfo) = Future.successful{ | |
CommonSocialProfile( | |
loginInfo = LoginInfo(ID, searchEntry.getAttributeValue("uid")), | |
firstName = Some(searchEntry.getAttributeValue("givenName")), | |
lastName = Some(searchEntry.getAttributeValue("sn")), | |
fullName = Some(searchEntry.getAttributeValue("givenName") + " "+ searchEntry.getAttributeValue("sn")), | |
email = Some(searchEntry.getAttributeValue("mail")), | |
avatarURL = None | |
) | |
} | |
} | |
class LDAPProvider (protected val httpLayer: HTTPLayer, val settings: LDAPSettings) | |
extends SocialProvider with CommonSocialProfileBuilder{ | |
/** | |
* The type of the auth info. | |
*/ | |
override type A = LDAPInfo | |
/** | |
* The settings type. | |
*/ | |
override type Settings = LDAPSettings | |
/** | |
* The content type to parse a profile from. | |
*/ | |
override type Content = SearchResultEntry | |
/** | |
* The provider ID. | |
*/ | |
override val id = "LDAP" | |
/** | |
* The type of this class. | |
*/ | |
type Self = LDAPProvider | |
/** | |
* The profile parser. | |
*/ | |
val profileParser = new LDAPProfileParser | |
/** | |
* Gets a provider initialized with a new settings object. | |
* | |
* @param f A function which gets the settings passed and returns different settings. | |
* @return An instance of the provider initialized with new settings. | |
*/ | |
def withSettings(f: (Settings) => Settings) = { | |
new LDAPProvider(httpLayer, f(settings)) | |
} | |
/** | |
* Defines the URLs that are needed to retrieve the profile data. | |
* this is not used in LDAP | |
*/ | |
override protected val urls = Map("hostname" -> settings.hostname) | |
val SpecifiedProfileError = "[Silhouette][%s] Error retrieving profile information. Error code: %s, message: %s" | |
val AuthorizationError = "[Silhouette][%s] Authorization server returned error: %s" | |
/** | |
* Builds the social profile. | |
* | |
* @param authInfo The auth info received from the provider. | |
* @return On success the build social profile, otherwise a failure. | |
*/ | |
override protected def buildProfile(authInfo: LDAPInfo): Future[Profile] = { | |
val username = authInfo.username | |
val password = authInfo.password | |
val baseUserNamespace = settings.userDN + "," + settings.baseDN | |
val baseGroupNamespace = settings.groupDN + "," + settings.baseDN | |
val trustManager = if (settings.trustAllCertificates) new TrustAllTrustManager() else JVMDefaultTrustManager.getInstance() | |
val sslUtil: SSLUtil = new SSLUtil(trustManager) | |
val socketFactory: SSLSocketFactory = sslUtil.createSSLSocketFactory() | |
var ldapConnection: LDAPConnection = null | |
try { | |
// Create LDAP connection | |
ldapConnection = new LDAPConnection(socketFactory, settings.hostname, settings.port) | |
// Bind user to the connection. | |
// This will throw an exception if the user credentials do not match any LDAP entry. | |
// This exception is later caught to refuse access to the user. | |
val dn = "uid=" + username + "," + baseUserNamespace | |
ldapConnection.bind(dn, password) | |
// Filter to search the user's membership in the specified group | |
val searchFilter: com.unboundid.ldap.sdk.Filter = com.unboundid.ldap.sdk.Filter.create("(&(objectClass=" + | |
settings.objectClass + ")(memberOf=cn=" + settings.group + "," + baseGroupNamespace + ")(uid=" + username + "))") | |
// Perform group membership search | |
val searchResult: SearchResult = ldapConnection.search(settings.baseDN, SearchScope.SUB, searchFilter) | |
// User is part of the specified group | |
if (searchResult.getEntryCount == 1) { | |
// Logger.debug("LDAP search result: " + searchResult.getSearchEntry(dn)) | |
val searchEntry = searchResult.getSearchEntry(dn) | |
profileParser.parse(searchEntry, authInfo) | |
} | |
// User is not part of the specified group | |
else { | |
throw new ProfileRetrievalException(SpecifiedProfileError.format(id, 403, "user not in the group")) | |
} | |
} catch { | |
case e: Exception => { | |
// TODO: change error code. | |
throw new ProfileRetrievalException(SpecifiedProfileError.format(id, 403, e.getMessage)) | |
} | |
} | |
finally { | |
// Close connection | |
if (ldapConnection != null) | |
ldapConnection.close() | |
} | |
} | |
def authenticate[B]()(implicit request: ExtractableRequest[B]): Future[Either[Result, LDAPInfo]] = { | |
request.extractString("error").map { | |
// TODO: may remove this part is not used. | |
// refer to https://github.com/mohiva/play-silhouette/blob/master/silhouette/app/com/mohiva/play/silhouette/impl/providers/OAuth2Provider.scala | |
case e @ "access_denied" => new AccessDeniedException(AuthorizationError.format(id, e)) | |
case e => new UnexpectedResponseException(AuthorizationError.format(id, e)) | |
} match { | |
case Some(throwable) => Future.failed(throwable) | |
case None => request.extractString("code") match { | |
// We're being redirected back from the authorization server with the access code and the state | |
case Some(code) => { | |
val authInfo = LDAPInfo(code = request.extractString("code").getOrElse(""), | |
username=request.extractString("username").getOrElse(""), | |
password=request.extractString("password").getOrElse("")) | |
Future.successful(Right(authInfo)) | |
} | |
// There's no code in the request, this is the first step in the OAuth flow, i.e. the webpage to fill in username | |
// & password. | |
case None => { | |
Future.successful(Left(Results.Redirect("ldapinput"))) | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment