Skip to content

Commit

Permalink
Merge pull request #36 from yetu/feature/scope-permission
Browse files Browse the repository at this point in the history
Feature/scope permission
  • Loading branch information
jschaul committed Jun 24, 2015
2 parents 89a769e + c3a4232 commit 3ce66d8
Show file tree
Hide file tree
Showing 48 changed files with 288 additions and 246 deletions.
5 changes: 3 additions & 2 deletions app/Global.scala
@@ -1,12 +1,14 @@
import com.softwaremill.macwire.{ Macwire, Wired }
import com.yetu.oauth2provider.utils.{ Config, CorsFilter }
import play.api.mvc.EssentialAction
import play.api.mvc.{ Result, RequestHeader, EssentialAction }

import com.yetu.common.YetuCommonGlobalSettings
import com.yetu.oauth2provider.registry._

import scala.concurrent.Future
object Global extends YetuCommonGlobalSettings with Macwire {


private val diRegistry: Wired = {
if (Config.persist) {
wiredInModule(PersistentControllerRegistry)
Expand All @@ -22,4 +24,3 @@ object Global extends YetuCommonGlobalSettings with Macwire {
override def doFilter(action: EssentialAction) = CorsFilter(action)

}

22 changes: 22 additions & 0 deletions app/assets/stylesheets/normal_permissions.css
@@ -0,0 +1,22 @@
.page-header {
margin-bottom: 15px;
}

.requestedPermissions {
max-height: 184px;
height: 184px;
display: block;
overflow-x: hidden;
overflow-y: auto;
margin-bottom: 15px;
}

.termsInfo {
margin-bottom: 15px;
}

.permissions__cancel {
text-align: right;
width: 100%;
display: block;
}
10 changes: 4 additions & 6 deletions app/com/yetu/oauth2provider/controllers/OAuth2Auth.scala
Expand Up @@ -6,7 +6,7 @@ import com.yetu.oauth2provider.models.{ Permission, Permissions }
import com.yetu.oauth2provider.oauth2.handlers
import com.yetu.oauth2provider.oauth2.models.{ AuthorizedClient, ClientPermission, OAuth2Client, YetuUser }
import com.yetu.oauth2provider.oauth2.services.{ AuthorizeErrorHandler, AuthorizeService }
import com.yetu.oauth2provider.services.data.iface.{ IClientService, IPermissionService }
import com.yetu.oauth2provider.services.data.interface.{ IClientService, IPermissionService }
import com.yetu.oauth2provider.utils.Config
import play.api.mvc._
import securesocial.core.RuntimeEnvironment
Expand Down Expand Up @@ -88,7 +88,7 @@ class OAuth2Auth(authorizationHandler: handlers.AuthorizationHandler,
if (client.coreYetuClient) {
authorizeService.handlePermittedApps(client, authorizeRequest, request.user)
} else {
authorizeService.handleClientPermissions(client, authorizeRequest, request.user)
authorizeService.handleClientPermissions(request, env, client, authorizeRequest, request.user)
}
}

Expand All @@ -101,13 +101,11 @@ class OAuth2Auth(authorizationHandler: handlers.AuthorizationHandler,

val clientOption = clientService.findClient(formData.client_id)
clientOption match {
case None => {
BadRequest(s"There is a problem with clientId=[${formData.client_id}]. It does not exist in our system")
}
case None => BadRequest(s"There is a problem with clientId=[${formData.client_id}]. It does not exist in our system")
case Some(client) => {
val clientPermission = ClientPermission(client.clientId, client.scopes)
permissionService.savePermission(request.user.email.get, clientPermission)
authorizeService.handlePermittedApp(client, Some(formData.redirect_uri), formData.state, None, request.user, clientPermission.scopes)
authorizeService.handlePermittedApp(client, formData.redirect_uri, formData.state, None, request.user, clientPermission.scopes)
}
}
}
Expand Down
Expand Up @@ -5,7 +5,7 @@ import com.yetu.oauth2provider.models.{ DataListWrapper, DataUpdateRequest }
import com.yetu.oauth2provider.models.HouseholdModel.householdFormat
import com.yetu.oauth2provider.oauth2.handlers.AuthorizationHandler
import com.yetu.oauth2provider.oauth2.services.ScopeService
import com.yetu.oauth2provider.services.data.iface.{ IPersonService, IPublicKeyService }
import com.yetu.oauth2provider.services.data.interface.{ IPersonService, IPublicKeyService }
import com.yetu.oauth2provider.signature.models.YetuPublicKey
import com.yetu.oauth2provider.utils.Config
import play.api.Logger
Expand Down
@@ -1,7 +1,7 @@
package com.yetu.oauth2provider
package controllers

import com.yetu.oauth2provider.services.data.iface.IPersonService
import com.yetu.oauth2provider.services.data.interface.IPersonService
import play.api.libs.json.{ Json, JsValue }
import play.api.mvc.{ Result, Action }
import com.yetu.oauth2provider.oauth2.services.ScopeService
Expand Down
@@ -1,7 +1,7 @@
package com.yetu.oauth2provider.controllers.authentication

import com.yetu.oauth2provider.oauth2.models.YetuUser
import com.yetu.oauth2provider.services.data.iface.IAuthCodeAccessTokenService
import com.yetu.oauth2provider.services.data.interface.IAuthCodeAccessTokenService
import com.yetu.oauth2provider.utils.StringUtils
import play.api.mvc._
import securesocial.controllers.BaseLoginPage
Expand Down
@@ -1,7 +1,7 @@
package com.yetu.oauth2provider.controllers.authentication

import com.yetu.oauth2provider.oauth2.models.YetuUser
import com.yetu.oauth2provider.services.data.iface.IPersonService
import com.yetu.oauth2provider.services.data.interface.IPersonService
import com.yetu.oauth2provider.utils.Config.SessionStatusCookie
import play.api.i18n.Messages
import play.api.mvc.{ Action, AnyContent, Cookie, Session }
Expand Down
Expand Up @@ -6,7 +6,7 @@ import java.util.Date
import com.yetu.oauth2provider.signature.SignatureHelper
import com.yetu.oauth2provider.signature.models.{ SignatureSyntaxException, SignatureException, SignedRequestHeaders, YetuPublicKey }

import com.yetu.oauth2provider.services.data.iface.{ IPublicKeyService, IPersonService }
import com.yetu.oauth2provider.services.data.interface.{ IPublicKeyService, IPersonService }
import com.yetu.oauth2provider.signature.services.SignatureService
import com.yetu.oauth2provider.utils.DateUtility
import net.adamcin.httpsig.api.{ Authorization, _ }
Expand Down
Expand Up @@ -4,7 +4,7 @@ import java.util.Date

import scalaoauth2.provider.AuthInfo
import com.yetu.oauth2provider.oauth2.models.YetuUser
import com.yetu.oauth2provider.services.data.iface.{ IAuthCodeAccessTokenService, IClientService, IPersonService }
import com.yetu.oauth2provider.services.data.interface.{ IAuthCodeAccessTokenService, IClientService, IPersonService }
import com.yetu.oauth2provider.utils.{ Config, JsonWebTokenGenerator, BearerTokenGenerator }
import play.api.Logger
import securesocial.core.providers.utils.PasswordHasher
Expand Down
Expand Up @@ -12,7 +12,7 @@ case class AuthorizeRequest(headers: Map[String, Seq[String]], params: Map[Strin

def state: String = requireParam(AuthorizeParameters.STATE)

def redirectUri: Option[String] = param(AuthorizeParameters.REDIRECT_URI)
def redirectUri: String = requireParam(AuthorizeParameters.REDIRECT_URI)

def scope: Option[String] = param(AuthorizeParameters.SCOPE)
}
121 changes: 74 additions & 47 deletions app/com/yetu/oauth2provider/oauth2/services/AuthorizeService.scala
Expand Up @@ -4,18 +4,20 @@ package services

import java.net.URLDecoder

import scalaoauth2.provider.AuthInfo
import com.yetu.oauth2provider.services.data.iface.{ IPermissionService, IPersonService, IAuthCodeAccessTokenService, IClientService }
import com.yetu.oauth2provider.models.Permission
import com.yetu.oauth2provider.oauth2.OAuth2Protocol._
import com.yetu.oauth2provider.oauth2.errors.InvalidState
import com.yetu.oauth2provider.oauth2.models._
import com.yetu.oauth2provider.services.data.interface.{ IAuthCodeAccessTokenService, IClientService, IPermissionService, IPersonService }
import com.yetu.oauth2provider.utils.Config.SessionStatusCookie
import play.api.mvc.{ Cookie, Controller, Result }
import com.yetu.oauth2provider.utils.{ BearerTokenGenerator, Config, NamedLogger }
import play.api.mvc.{ Controller, Cookie, RequestHeader, Result }
import securesocial.core.RuntimeEnvironment
import securesocial.core.authenticator.CookieAuthenticator

import scala.concurrent.Future
import scalaoauth2.provider
import scalaoauth2.provider._
import OAuth2Protocol._
import com.yetu.oauth2provider.oauth2.models._
import errors.InvalidState
import com.yetu.oauth2provider.utils.{ NamedLogger, Config, BearerTokenGenerator }
import scalaoauth2.provider.{ AuthInfo, _ }

class AuthorizeErrorHandler(clientService: IClientService,
personService: IPersonService,
Expand All @@ -42,37 +44,39 @@ class AuthorizeErrorHandler(clientService: IClientService,
throw new InvalidState(s"invalid state parameter. State length is not correct.")
}

val client = clientService.findClient(request.clientId).getOrElse(throw new InvalidClient(s"client_id '${request.clientId}' does not exist"))
val client = clientService
.findClient(request.clientId)
.getOrElse(throw new InvalidClient(s"client_id '${request.clientId}' does not exist"))

val validScopes: List[String] = if (client.coreYetuClient) {
client.scopes.getOrElse(List.empty)
} else {
scopeService.getScopeFromPermission(permissionService.findPermission(user.identityId.userId, client.clientId))
}
val requestScopeString = request.scope.getOrElse(Config.SCOPE_ID)
val validScopes: List[String] = client.scopes.getOrElse(List.empty)

val requestScopes: List[String] = requestScopeString.split(' ').toList
if (!client.coreYetuClient) {
scopeService.getScopeFromPermission(
permissionService.findPermission(user.identityId.userId, client.clientId))
}

requestScopes.foreach { requestScope =>
if (!validScopes.contains(requestScope)) {
throw new InvalidScope(s"invalid scope: $requestScope")
request.scope.foreach { scope =>
scope.split(' ').toList.foreach { requestScope =>
if (!validScopes.contains(requestScope)) {
throw new InvalidScope(s"invalid scope: $requestScope")
}
}
}

val validRedirectUrls = client.redirectURIs

//If there is no redirect url in the request then we fetch the first url from LDAP as a default one
val redirectUrl = URLDecoder.decode(request.redirectUri.getOrElse(validRedirectUrls.head), "UTF-8")
val redirectUrl = URLDecoder.decode(request.redirectUri, "UTF-8")

if (!validRedirectUrls.contains(redirectUrl)) {
logger.warn(s"clientID:[${client.clientId}] request redirect url is NOT VALID! [$redirectUrl]. Only allowed ones are : $validRedirectUrls}")

logger.warn(s"clientID:[${client.clientId}] request redirect url is NOT VALID! " +
s"[$redirectUrl]. Only allowed ones are : $validRedirectUrls}")

if (Config.redirectURICheckingEnabled) {
throw new RedirectUriMismatch(s"invalid redirect url.")
}
}

val authorizedClient = AuthorizedClient(client, request, redirectUrl)

Right(authorizedClient)

} catch {
Expand Down Expand Up @@ -101,32 +105,26 @@ class AuthorizeService(authAccessService: IAuthCodeAccessTokenService,
scopeService: ScopeService,
permissionService: IPermissionService) extends Controller {

def handlePermittedApp(client: OAuth2Client, redirectUri: Option[String], state: String, scopeFromRequest: Option[String], user: YetuUser, userDefinedScopes: Option[List[String]] = None) = {
def handlePermittedApp(client: OAuth2Client,
redirectUri: String,
state: String,
scopeFromRequest: Option[String],
user: YetuUser,
userDefinedScopes: Option[List[String]]) = {

val auth_code = BearerTokenGenerator.generateToken(Config.OAuth2.authTokenLength)
val queryString: Map[String, Seq[String]] = Map(
ResponseTypes.CODE -> Seq(auth_code),
AuthorizeParameters.STATE -> Seq(state)
)

/*
Get the scope the user has defined when granting permissions;
if the user did not set any (because of core yetu app), get the scope of the request if it exists;
fallback to default scope of a certain client;
fallback to the most basic ID scope
*/
val scope = scopeService.getFirstScope(userDefinedScopes).
getOrElse(scopeFromRequest.
getOrElse(scopeService.getFirstScope(client.scopes).
getOrElse(Config.SCOPE_ID)))

val redirectUrl = redirectUri.getOrElse(client.redirectURIs.head)
val scope = if (userDefinedScopes.isDefined) userDefinedScopes.map(_.mkString(" ")) else scopeFromRequest

authAccessService.saveAuthCode(
auth_code,
new AuthInfo[YetuUser](user, Some(client.clientId), Some(scope), Some(redirectUrl)))
new AuthInfo[YetuUser](user, Some(client.clientId), scope, Some(redirectUri)))

Redirect(redirectUrl, queryString).withCookies(getAdditionalSessionStateCookie(user.userId))
Redirect(redirectUri, queryString).withCookies(getAdditionalSessionStateCookie(user.userId))
}

def getAdditionalSessionStateCookie(userId: String): Cookie = {
Expand All @@ -138,26 +136,55 @@ class AuthorizeService(authAccessService: IAuthCodeAccessTokenService,
userUUID,
if (CookieAuthenticator.makeTransient)
CookieAuthenticator.Transient
else Some(CookieAuthenticator.absoluteTimeoutInSeconds),
else
Some(CookieAuthenticator.absoluteTimeoutInSeconds),
SessionStatusCookie.cookiePath,
SessionStatusCookie.cookieDomain,
secure = SessionStatusCookie.cookieSecure,
httpOnly = SessionStatusCookie.cookieHttpOnly
)
}

def handlePermittedApps(client: OAuth2Client, authorizeRequest: AuthorizeRequest, user: YetuUser, userDefinedScopes: Option[List[String]] = None): Result = {
handlePermittedApp(client, authorizeRequest.redirectUri, authorizeRequest.state, authorizeRequest.scope, user, userDefinedScopes)
def handlePermittedApps(client: OAuth2Client,
authorizeRequest: AuthorizeRequest,
user: YetuUser,
userDefinedScopes: Option[List[String]] = None): Result = {

handlePermittedApp(
client,
authorizeRequest.redirectUri,
authorizeRequest.state,
authorizeRequest.scope,
user,
userDefinedScopes)
}

def handleClientPermissions(client: OAuth2Client, authorizeRequest: AuthorizeRequest, user: YetuUser): Result = {
def handleClientPermissions(request: RequestHeader,
env: RuntimeEnvironment[YetuUser],
client: OAuth2Client,
authorizeRequest: AuthorizeRequest,
user: YetuUser): Result = {

val clientPermission: Option[ClientPermission] = permissionService.findPermission(user.identityId.userId, client.clientId)
clientPermission match {
case None =>
//TODO: This should be implemented
//Ok(com.yetu.oauth2provider.views.html.permissions(permissionsForm, client.clientName, Some(client.clientId), authorizeRequest.redirectUri, Some(authorizeRequest.state)))
Ok("OK")
case Some(permission) => handlePermittedApps(client, authorizeRequest, user, userDefinedScopes = permission.scopes)

Ok(com.yetu.oauth2provider.views.html.permissions(
Permission.permissionsForm,
client.clientName,
client.clientId,
client.scopes.getOrElse(List.empty[String]),
authorizeRequest.redirectUri,
Some(authorizeRequest.state))(request, env))

case Some(permission) =>
/*
* TODO:
* here we can consider the scope from the url, if the scope on the url is not included
* in the client.scopes means that the application is trying to ask for more permissions then
* the one that is allowed to it.. this is the incremental permission process
*/
handlePermittedApps(client, authorizeRequest, user, userDefinedScopes = permission.scopes)
}
}

Expand Down
@@ -1,7 +1,7 @@
package com.yetu.oauth2provider.oauth2.services

import com.yetu.oauth2provider.oauth2.models.{ ImplicitFlowSyntaxException, ImplicitFlowException, YetuUser }
import com.yetu.oauth2provider.services.data.iface.IPersonService
import com.yetu.oauth2provider.services.data.interface.IPersonService
import play.api.Logger

import scala.concurrent.Future
Expand Down
13 changes: 9 additions & 4 deletions app/com/yetu/oauth2provider/registry/ServicesRegistry.scala
Expand Up @@ -10,12 +10,14 @@ import com.yetu.oauth2provider.oauth2.OAuth2TokenEndpoint
import com.yetu.oauth2provider.oauth2.handlers.AuthorizationHandler
import com.yetu.oauth2provider.oauth2.models.YetuUser
import com.yetu.oauth2provider.oauth2.services._
import com.yetu.oauth2provider.services.data.{ MemoryUserService, _ }
import com.yetu.oauth2provider.services.data.iface._
import com.yetu.oauth2provider.services.data.interface._
import com.yetu.oauth2provider.services.data.ldap._
import com.yetu.oauth2provider.services.data.memory._
import com.yetu.oauth2provider.services.data.riak.{ RiakAuthCodeAccessTokens, RiakAuthenticatorStore, RiakMailTokenService }
import com.yetu.oauth2provider.signature.services.SignatureService
import com.yetu.oauth2provider.utils.Config.RiakSettings
import com.yetu.oauth2provider.utils.JsonWebTokenGenerator
import securesocial.core.authenticator.{ HttpHeaderAuthenticator, AuthenticatorStore }
import securesocial.core.authenticator.{ AuthenticatorStore, HttpHeaderAuthenticator }
import securesocial.core.providers.utils.PasswordHasher
import securesocial.core.services.{ CacheService, UserService }

Expand Down Expand Up @@ -47,7 +49,10 @@ trait PersistentDataServices {

lazy val dao: LdapDAO = wire[LdapDAO]
lazy val clientService: IClientService = wire[LdapClientService]
lazy val permissionService: IPermissionService = wire[LdapPermissionService]

//TODO: change this after LDAP is not in use anymore to use the new API.
//TODO: LDAP permission service is actually broken, do not use.
lazy val permissionService: IPermissionService = wire[MemoryPermissionService]

lazy val publicKeyService: IPublicKeyService = new LdapPublicKeyService(new LdapPersonService(dao))

Expand Down
@@ -0,0 +1,24 @@
package com.yetu.oauth2provider.services.data.api

import com.yetu.oauth2provider.oauth2.models.OAuth2Client
import com.yetu.oauth2provider.services.data.interface.IClientService

class APIClientService() extends IClientService {

def saveClient(client: OAuth2Client, ignoreEntryAlreadyExists: Boolean): Unit = {

}

def findClient(clientId: String): Option[OAuth2Client] = {
None
}

def deleteClient(client: OAuth2Client) = {

}

def deleteClient(clientId: String) = {

}

}

0 comments on commit 3ce66d8

Please sign in to comment.