-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathIam.scala
147 lines (128 loc) · 3.96 KB
/
Iam.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
package com.gu.janus.policy
import io.circe.syntax._
import io.circe.{Encoder, Json, JsonObject}
import scala.collection.immutable.ListMap
/** This models the AWS IAM policy types and subtypes.
*
* See https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html
* for the logic of the Json encoding.
*/
object Iam {
sealed trait Effect
object Effect {
case object Allow extends Effect
case object Deny extends Effect
implicit val encoder: Encoder[Effect] = Encoder.instance {
case Allow => Json.fromString("Allow")
case Deny => Json.fromString("Deny")
}
}
case class Action(name: String)
object Action {
implicit val encoder: Encoder[Action] = Encoder[String].contramap(_.name)
}
case class Resource(id: String)
object Resource {
implicit val encoder: Encoder[Resource] = Encoder[String].contramap(_.id)
}
case class Condition(
key: String,
typeName: String,
conditionValues: Seq[String]
)
object Condition {
implicit val encoder: Encoder[Condition] = Encoder.instance { condition =>
Json.obj(
condition.typeName -> Json.obj(
condition.key -> Json.fromValues(
condition.conditionValues.map(Json.fromString)
)
)
)
}
}
case class Principal(id: String, provider: String)
object Principal {
implicit val encoder: Encoder[Principal] = Encoder.instance { principal =>
Json.obj(
principal.provider -> Json.fromString(principal.id)
)
}
}
case class Statement(
effect: Effect,
actions: Seq[Action],
resources: Seq[Resource],
id: Option[String] = None,
conditions: Seq[Condition] = Nil,
principals: Seq[Principal] = Nil
)
object Statement {
private def conditionsAsJson(conditions: Seq[Condition]): Json = {
val mergedConditions = conditions
.groupBy(_.typeName)
.view
.mapValues(conditions =>
Json.obj(
conditions.map(condition =>
condition.key -> Json.fromValues(
condition.conditionValues.map(Json.fromString)
)
): _*
)
)
.toMap
Json.fromJsonObject(JsonObject.fromMap(mergedConditions))
}
private def principalsAsJson(principals: Seq[Principal]): Json = {
val principalMap = principals
.groupBy(_.provider)
.view
.mapValues(principals =>
Json.fromValues(
principals.map(p => Json.fromString(p.id))
)
)
.toMap
Json.fromJsonObject(JsonObject.fromMap(principalMap))
}
implicit val encoder: Encoder[Statement] = Encoder.instance { statement =>
// Using ListMap to preserve order of fields, which is easier to debug
val baseFields = ListMap(
"Effect" -> statement.effect.asJson,
"Action" -> statement.actions.asJson,
"Resource" -> statement.resources.asJson
)
val withSid = statement.id.fold(baseFields)(id =>
baseFields + ("Sid" -> Json.fromString(id))
)
val withConditions =
if (statement.conditions.nonEmpty)
withSid + ("Condition" -> conditionsAsJson(statement.conditions))
else withSid
val withPrincipals =
if (statement.principals.nonEmpty)
withConditions + ("Principal" -> principalsAsJson(
statement.principals
))
else withConditions
Json.fromJsonObject(JsonObject.fromMap(withPrincipals))
}
}
case class Policy(
statements: Seq[Statement],
id: Option[String] = None
)
object Policy {
private val PolicyVersion = "2012-10-17"
implicit val encoder: Encoder[Policy] = Encoder.instance { policy =>
val baseObj = JsonObject(
"Version" -> Json.fromString(PolicyVersion),
"Statement" -> policy.statements.asJson
)
Json.fromJsonObject(
policy.id.fold(baseObj)(id => baseObj.add("Id", Json.fromString(id)))
)
}
}
}