Skip to content
This repository has been archived by the owner on Apr 10, 2019. It is now read-only.

Commit

Permalink
#3, refactor: Correct handling of swagger security urls
Browse files Browse the repository at this point in the history
  • Loading branch information
slavaschmidt committed Apr 6, 2016
1 parent 2a0c2fb commit ba3da37
Show file tree
Hide file tree
Showing 24 changed files with 54 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ object SwaggerSecurityExtractors extends BasicAuthSecurityExtractor with OAuthRe
def queryApiKey[User >: Any]: String => RequestHeader => (String => User) => Future[Option[User]] =
name => header => convertToUser => header.queryString.get(name).flatMap(_.headOption) map convertToUser

def oAuthPassword[User >: Any]: Seq[String] => String => RequestHeader => (JsValue => User) => Future[Option[User]] =
def oAuth[User >: Any]: Seq[String] => String => RequestHeader => (JsValue => User) => Future[Option[User]] =
scopes => tokenUrl => header => convertToUser => {
val futureResult = header.headers.get("Authorization").flatMap(decodeBearer).map { token: String =>
checkOAuthToken(tokenUrl, token, scopes:_*)
Expand All @@ -66,8 +66,6 @@ object SwaggerSecurityExtractors extends BasicAuthSecurityExtractor with OAuthRe
futureResult.map(_.map(convertToUser))
}

def oAuth[User >: Any]: (String*) => RequestHeader => (Seq[String] => User) => Future[Option[User]] =
scopes => header => convertUer => ???
}

trait BasicAuthSecurityExtractor {
Expand Down
23 changes: 3 additions & 20 deletions compiler/src/main/scala/de/zalando/apifirst/ast.scala
Original file line number Diff line number Diff line change
Expand Up @@ -224,31 +224,14 @@ object Security {
sealed trait Definition {
def description: Option[String]
}
sealed trait OAuth2Definition extends Definition {
def description: Option[String]
def scopes: OAuth2Scopes
}
case class Basic(description: Option[String]) extends Definition
case class ApiKey(description: Option[String], name: String, in: ParameterPlace) extends Definition {
require(in == ParameterPlace.QUERY || in == ParameterPlace.HEADER)
require(name.nonEmpty)
}
case class OAuth2Implicit(description: Option[String], authorizationUrl: URL,
scopes: OAuth2Scopes) extends OAuth2Definition {
require(authorizationUrl != null)
}
case class OAuth2Password(description: Option[String], tokenUrl: URL,
scopes: OAuth2Scopes) extends OAuth2Definition {
require(tokenUrl != null)
}
case class OAuth2Application(description: Option[String], tokenUrl: URL,
scopes: OAuth2Scopes) extends OAuth2Definition {
require(tokenUrl != null)
}
case class OAuth2AccessCode(description: Option[String], authorizationUrl: URL, tokenUrl: URL,
scopes: OAuth2Scopes) extends OAuth2Definition {
require(tokenUrl != null)
require(authorizationUrl != null)
case class OAuth2Definition(description: Option[String], validationURL: Option[URL],
scopes: OAuth2Scopes) extends Definition {
require(validationURL != null)
}

sealed trait Constraint {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,15 @@ trait SecurityStep extends EnrichmentStep[StrictModel] with SecurityCommons {
case b: Basic => "basicAuth"
case ApiKey(_, _, in) if in == ParameterPlace.HEADER => "headerApiKey"
case ApiKey(_, _, in) if in == ParameterPlace.QUERY => "queryApiKey"
case o: OAuth2Password => "oAuthPassword"
case o: OAuth2Application => "oAuthPassword"
case o: OAuth2Implicit => "oAuth" // FIXME
case o: OAuth2AccessCode => "oAuth" // FIXME
case o: OAuth2Definition => "oAuth"
}

private def securityParams(definition: Definition): Seq[Map[String,String]] = definition match {
case b: Basic => Nil
case ApiKey(_, name, _) => Seq(Map("name" -> ("\"" + name + "\"")))
case OAuth2Password(_, tokenUrl, scopes) => Seq(Map("name" -> ("\"" + tokenUrl + "\"")))
case OAuth2Application(_, tokenUrl, scopes) => Seq(Map("name" -> ("\"" + tokenUrl + "\"")))

case _ => Nil // FIXME
case OAuth2Definition(_, None, _) =>
throw new IllegalStateException("Validation URL is required for play security code generator")
case OAuth2Definition(_, Some(validationURL), scopes) => Seq(Map("name" -> ("\"" + validationURL + "\"")))
}

private def externalSecurityParams(definition: Definition): Seq[Map[String,String]] = definition match {
Expand All @@ -58,9 +54,7 @@ trait SecurityStep extends EnrichmentStep[StrictModel] with SecurityCommons {
private def userParams(definition: Definition): Seq[Map[String,String]] = definition match {
case b: Basic => Seq(Map("name" -> "username", "type" -> "String"), Map("name" -> "password", "type" -> "String"))
case ApiKey(_, name, _) => Seq(Map("name" -> "apiKey", "type" -> "String"))
case OAuth2Password(_, tokenUrl, scopes) => Seq(Map("name" -> "token", "type" -> "play.api.libs.json.JsValue"))
case OAuth2Application(_, tokenUrl, scopes) => Seq(Map("name" -> "token", "type" -> "play.api.libs.json.JsValue"))
case _ => Nil // FIXME
case OAuth2Definition(_, tokenUrl, scopes) => Seq(Map("name" -> "token", "type" -> "play.api.libs.json.JsValue"))
}
}

Expand Down
16 changes: 3 additions & 13 deletions compiler/src/main/scala/de/zalando/swagger/SecurityConverter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,9 @@ object SecurityConverter {
val place = ParameterPlace.withName(apiKey.in.toLowerCase)
require(place == ParameterPlace.HEADER || place == ParameterPlace.QUERY)
name -> Security.ApiKey(Option(apiKey.description), apiKey.name, place)
case (name, oauth: Oauth2ImplicitSecurity) =>
val authorizationUrl = new URL(oauth.authorizationUrl)
name -> Security.OAuth2Implicit(Option(oauth.description), authorizationUrl, oauth.scopes)
case (name, oauth: Oauth2PasswordSecurity) =>
val tokenUrl = new URL(oauth.tokenUrl)
name -> Security.OAuth2Password(Option(oauth.description), tokenUrl, oauth.scopes)
case (name, oauth: Oauth2ApplicationSecurity) =>
val tokenUrl = new URL(oauth.tokenUrl)
name -> Security.OAuth2Application(Option(oauth.description), tokenUrl, oauth.scopes)
case (name, oauth: Oauth2AccessCodeSecurity) =>
val authorizationUrl = new URL(oauth.authorizationUrl)
val tokenUrl = new URL(oauth.tokenUrl)
name -> Security.OAuth2AccessCode(Option(oauth.description), authorizationUrl, tokenUrl, oauth.scopes)
case (name, oauth: Oauth2SecurityDefinition) =>
val validationURL = oauth.validationUrl.map(new URL(_))
name -> Security.OAuth2Definition(Option(oauth.description), validationURL, oauth.scopes)
}
}

Expand Down
14 changes: 10 additions & 4 deletions compiler/src/main/scala/de/zalando/swagger/strictModel.scala
Original file line number Diff line number Diff line change
Expand Up @@ -748,6 +748,12 @@ object strictModel {
/********* security definitions *********/
sealed trait SecurityDefinition extends VendorExtensions

sealed trait Oauth2SecurityDefinition extends SecurityDefinition {
lazy val validationUrl: Option[Uri] = vendorExtensions.get("x-token-validation-url")
def description: String
def scopes: Oauth2Scopes
}

case class BasicAuthenticationSecurity(
@JsonProperty(required = true) `type`: String, // "enum": basic
description: Description
Expand All @@ -766,7 +772,7 @@ object strictModel {
@JsonProperty(required = true) authorizationUrl: Uri,
scopes: Oauth2Scopes,
description: Description
) extends SecurityDefinition with UriChecker {
) extends Oauth2SecurityDefinition with UriChecker {
val url = authorizationUrl
}

Expand All @@ -776,7 +782,7 @@ object strictModel {
@JsonProperty(required = true) tokenUrl: Uri,
scopes: Oauth2Scopes,
description: Description
) extends SecurityDefinition with UriChecker {
) extends Oauth2SecurityDefinition with UriChecker {
val url = tokenUrl
}

Expand All @@ -786,7 +792,7 @@ object strictModel {
@JsonProperty(required = true) tokenUrl: Uri,
scopes: Oauth2Scopes,
description: Description
) extends SecurityDefinition with UriChecker {
) extends Oauth2SecurityDefinition with UriChecker {
val url = tokenUrl
}

Expand All @@ -797,7 +803,7 @@ object strictModel {
@JsonProperty(required = true) tokenUrl: Uri,
scopes: Oauth2Scopes,
description: Description
) extends SecurityDefinition with UriChecker {
) extends Oauth2SecurityDefinition with UriChecker {
val url = tokenUrl
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,7 @@ securityDefinitions:
petstore_auth:
type: oauth2
authorizationUrl: http://petstore.swagger.wordnik.com/api/oauth/dialog
x-token-validation-url: http://petstore.swagger.wordnik.com/oauth/dialog
flow: implicit
scopes:
write_pets: modify pets in your account
Expand Down
1 change: 1 addition & 0 deletions compiler/src/test/resources/examples/instagram.api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,7 @@ securityDefinitions:
type: oauth2
flow: implicit
authorizationUrl: https://instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token
x-token-validation-url: https://instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token
scopes:
basic: |
to read any and all data related to a user (e.g. following/followed-by
Expand Down
4 changes: 4 additions & 0 deletions compiler/src/test/resources/examples/security.api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ securityDefinitions:
flow: accessCode
authorizationUrl: https://github.com/login/oauth/authorize
tokenUrl: https://github.com/login/oauth/access_token
x-token-validation-url: https://github.com/login/oauth/access_token
petstoreImplicit:
type: oauth2
scopes:
Expand All @@ -144,20 +145,23 @@ securityDefinitions:
admin:public_key: Fully manage public keys.
flow: implicit
authorizationUrl: http://petstore.swagger.wordnik.com/oauth/dialog
x-token-validation-url: http://petstore.swagger.wordnik.com/oauth/dialog
petstorePassword:
type: oauth2
scopes:
user: Grants read/write access to profile
admin: Fully manage
flow: password
tokenUrl: http://petstore.swagger.wordnik.com/oauth/dialog
x-token-validation-url: http://petstore.swagger.wordnik.com/oauth/dialog
petstoreApplication:
type: oauth2
scopes:
user: Grants read/write access to profile
admin: Fully manage
flow: application
tokenUrl: http://petstore.swagger.wordnik.com/oauth/token
x-token-validation-url: http://petstore.swagger.wordnik.com/oauth/token
internalApiKey:
type: apiKey
in: header
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,7 @@ securityDefinitions:
petstore_auth:
type: oauth2
authorizationUrl: http://petstore.swagger.wordnik.com/api/oauth/dialog
x-token-validation-url: http://petstore.swagger.wordnik.com/oauth/dialog
flow: implicit
scopes:
write_pets: modify pets in your account
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ trait SecurityExtractors {
???
}
def petstore_auth_Extractor[User >: Any](scopes: String*): RequestHeader => Future[Option[User]] =
header => oAuth(scopes)(header) { _ =>
header => oAuth(scopes)("http://petstore.swagger.wordnik.com/oauth/dialog")(header) { (token: play.api.libs.json.JsValue) =>
???
}
implicit val unauthorizedContentWriter = ???
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import de.zalando.play.controllers.SwaggerSecurityExtractors._

trait SecurityExtractors {
def oauth_Extractor[User >: Any](scopes: String*): RequestHeader => Future[Option[User]] =
header => oAuth(scopes)(header) { _ =>
header => oAuth(scopes)("https://instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token")(header) { (token: play.api.libs.json.JsValue) =>
???
}
def key_Extractor[User >: Any](): RequestHeader => Future[Option[User]] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,23 @@ import de.zalando.play.controllers.ArrayWrapper

trait SecurityExtractors {
def petstoreImplicit_Extractor[User >: Any](scopes: String*): RequestHeader => Future[Option[User]] =
header => oAuth(scopes)(header) { _ =>
header => oAuth(scopes)("http://petstore.swagger.wordnik.com/oauth/dialog")(header) { (token: play.api.libs.json.JsValue) =>
???
}
def githubAccessCode_Extractor[User >: Any](scopes: String*): RequestHeader => Future[Option[User]] =
header => oAuth(scopes)(header) { _ =>
header => oAuth(scopes)("https://github.com/login/oauth/access_token")(header) { (token: play.api.libs.json.JsValue) =>
???
}
def petstorePassword_Extractor[User >: Any](scopes: String*): RequestHeader => Future[Option[User]] =
header => oAuthPassword(scopes)("http://petstore.swagger.wordnik.com/oauth/dialog")(header) { (token: play.api.libs.json.JsValue) =>
header => oAuth(scopes)("http://petstore.swagger.wordnik.com/oauth/dialog")(header) { (token: play.api.libs.json.JsValue) =>
???
}
def justBasicStuff_Extractor[User >: Any](): RequestHeader => Future[Option[User]] =
header => basicAuth(header) { (username: String, password: String) =>
???
}
def petstoreApplication_Extractor[User >: Any](scopes: String*): RequestHeader => Future[Option[User]] =
header => oAuthPassword(scopes)("http://petstore.swagger.wordnik.com/oauth/token")(header) { (token: play.api.libs.json.JsValue) =>
header => oAuth(scopes)("http://petstore.swagger.wordnik.com/oauth/token")(header) { (token: play.api.libs.json.JsValue) =>
???
}
def internalApiKey_Extractor[User >: Any](): RequestHeader => Future[Option[User]] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ trait SecurityExtractors {
???
}
def petstore_auth_Extractor[User >: Any](scopes: String*): RequestHeader => Future[Option[User]] =
header => oAuth(scopes)(header) { _ =>
header => oAuth(scopes)("http://petstore.swagger.wordnik.com/oauth/dialog")(header) { (token: play.api.libs.json.JsValue) =>
???
}
implicit val unauthorizedContentWriter = ???
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
OAuth2Constraint(petstore_auth,OAuth2Implicit(None,http://petstore.swagger.wordnik.com/api/oauth/dialog,Map(write_pets -> modify pets in your account, read_pets -> read your pets)),Set(write_pets, read_pets))
OAuth2Constraint(petstore_auth,OAuth2Definition(None,Some(http://petstore.swagger.wordnik.com/oauth/dialog),Map(write_pets -> modify pets in your account, read_pets -> read your pets)),Set(write_pets, read_pets))
ApiKeyConstraint(api_key,ApiKey(None,api_key,header))
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
OAuth2Constraint(oauth,OAuth2Implicit(None,https://instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token,Map(basic -> to read any and all data related to a user (e.g. following/followed-by
OAuth2Constraint(oauth,OAuth2Definition(None,Some(https://instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token),Map(basic -> to read any and all data related to a user (e.g. following/followed-by
lists, photos, etc.) (granted by default)
, comments -> to create or delete comments on a user’s behalf, relationships -> to follow and unfollow users on a user’s behalf, likes -> to like and unlike items on a user’s behalf)),Set(basic, comments, relationships, likes))
ApiKeyConstraint(key,ApiKey(None,access_token,query))
OAuth2Constraint(oauth,OAuth2Implicit(None,https://instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token,Map(basic -> to read any and all data related to a user (e.g. following/followed-by
OAuth2Constraint(oauth,OAuth2Definition(None,Some(https://instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token),Map(basic -> to read any and all data related to a user (e.g. following/followed-by
lists, photos, etc.) (granted by default)
, comments -> to create or delete comments on a user’s behalf, relationships -> to follow and unfollow users on a user’s behalf, likes -> to like and unlike items on a user’s behalf)),Set(comments))
OAuth2Constraint(oauth,OAuth2Implicit(None,https://instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token,Map(basic -> to read any and all data related to a user (e.g. following/followed-by
OAuth2Constraint(oauth,OAuth2Definition(None,Some(https://instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token),Map(basic -> to read any and all data related to a user (e.g. following/followed-by
lists, photos, etc.) (granted by default)
, comments -> to create or delete comments on a user’s behalf, relationships -> to follow and unfollow users on a user’s behalf, likes -> to like and unlike items on a user’s behalf)),Set(relationships))
OAuth2Constraint(oauth,OAuth2Implicit(None,https://instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token,Map(basic -> to read any and all data related to a user (e.g. following/followed-by
OAuth2Constraint(oauth,OAuth2Definition(None,Some(https://instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token),Map(basic -> to read any and all data related to a user (e.g. following/followed-by
lists, photos, etc.) (granted by default)
, comments -> to create or delete comments on a user’s behalf, relationships -> to follow and unfollow users on a user’s behalf, likes -> to like and unlike items on a user’s behalf)),Set(basic))
Loading

0 comments on commit ba3da37

Please sign in to comment.