@@ -26,6 +26,8 @@ defmodule Assent.Strategy do
26
26
end
27
27
end
28
28
"""
29
+ alias Assent.CastClaimsError
30
+
29
31
@ callback authorize_url ( Keyword . t ( ) ) ::
30
32
{ :ok , % { :url => binary ( ) , optional ( atom ( ) ) => any ( ) } } | { :error , term ( ) }
31
33
@ callback callback ( Keyword . t ( ) , map ( ) ) ::
@@ -124,25 +126,117 @@ defmodule Assent.Strategy do
124
126
125
127
defp encode_value ( value ) , do: URI . encode_www_form ( Kernel . to_string ( value ) )
126
128
129
+ @ registered_claim_members % {
130
+ "sub" => :binary ,
131
+ "name" => :binary ,
132
+ "given_name" => :binary ,
133
+ "family_name" => :binary ,
134
+ "middle_name" => :binary ,
135
+ "nickname" => :binary ,
136
+ "preferred_username" => :binary ,
137
+ "profile" => :binary ,
138
+ "picture" => :binary ,
139
+ "website" => :binary ,
140
+ "email" => :binary ,
141
+ "email_verified" => :boolean ,
142
+ "gender" => :binary ,
143
+ "birthdate" => :binary ,
144
+ "zoneinfo" => :binary ,
145
+ "locale" => :binary ,
146
+ "phone_number" => :binary ,
147
+ "phone_number_verified" => :boolean ,
148
+ "address" => % {
149
+ "formatted" => :binary ,
150
+ "street_address" => :binary ,
151
+ "locality" => :binary ,
152
+ "region" => :binary ,
153
+ "postal_code" => :binary ,
154
+ "country" => :binary
155
+ } ,
156
+ "updated_at" => :integer
157
+ }
158
+
127
159
@ doc """
128
160
Normalize API user request response into standard claims.
129
161
162
+ The function will cast values to adhere to the following types:
163
+
164
+ ```
165
+ #{ inspect ( @ registered_claim_members , pretty: true ) }
166
+ ```
167
+
168
+ Returns a `Assent.CastClaimsError` if any of the above types can't be casted.
169
+
130
170
Based on https://openid.net/specs/openid-connect-core-1_0.html#rfc.section.5.1
131
171
"""
132
- @ spec normalize_userinfo ( map ( ) , map ( ) ) :: { :ok , map ( ) }
172
+ @ spec normalize_userinfo ( map ( ) , map ( ) ) :: { :ok , map ( ) } | { :error , term ( ) }
133
173
def normalize_userinfo ( claims , extra \\ % { } ) do
134
- standard_claims =
135
- Map . take (
136
- claims ,
137
- ~w( sub name given_name family_name middle_name nickname
138
- preferred_username profile picture website email email_verified
139
- gender birthdate zoneinfo locale phone_number phone_number_verified
140
- address updated_at)
141
- )
174
+ case traverse_claims ( @ registered_claim_members , claims ) do
175
+ { claims , nil } -> { :ok , Map . merge ( prune ( extra ) , claims || % { } ) }
176
+ { _claims , invalid_claims } -> { :error , CastClaimsError . exception ( claims: invalid_claims ) }
177
+ end
178
+ end
179
+
180
+ defp traverse_claims ( claim_members , claims ) do
181
+ claim_members
182
+ |> Enum . reduce ( { [ ] , [ ] } , & traverse_claims ( & 1 , & 2 , Map . get ( claims , elem ( & 1 , 0 ) ) ) )
183
+ |> then ( fn { claims , invalid_claims } ->
184
+ {
185
+ ( claims != [ ] && Enum . into ( claims , % { } ) ) || nil ,
186
+ ( invalid_claims != [ ] && Enum . into ( invalid_claims , % { } ) ) || nil
187
+ }
188
+ end )
189
+ end
190
+
191
+ defp traverse_claims ( _ , { valid_claims , invalid_claims } , nil ) , do: { valid_claims , invalid_claims }
192
+
193
+ defp traverse_claims (
194
+ { key , % { } = claim_members } ,
195
+ { parent_valid_claims , parent_invalid_claims } ,
196
+ % { } = claims
197
+ ) do
198
+ case traverse_claims ( claim_members , claims ) do
199
+ { nil , nil } ->
200
+ { parent_valid_claims , parent_invalid_claims }
201
+
202
+ { claims , nil } ->
203
+ { [ { key , claims } | parent_valid_claims ] , parent_invalid_claims }
204
+
205
+ { nil , invalid_claims } ->
206
+ { parent_valid_claims , [ { key , invalid_claims } | parent_invalid_claims ] }
142
207
143
- { :ok , prune ( Map . merge ( extra , standard_claims ) ) }
208
+ { claims , invalid_claims } ->
209
+ { [ { key , claims } | parent_valid_claims ] , [ { key , invalid_claims } | parent_invalid_claims ] }
210
+ end
211
+ end
212
+
213
+ defp traverse_claims ( { key , % { } } , { valid_claims , invalid_claims } , claims ) do
214
+ { valid_claims , [ { key , { :map , claims } } | invalid_claims ] }
144
215
end
145
216
217
+ defp traverse_claims ( { key , type } , { valid_claims , invalid_claims } , value ) do
218
+ case cast_value ( value , type ) do
219
+ { :ok , value } -> { [ { key , value } | valid_claims ] , invalid_claims }
220
+ :error -> { valid_claims , [ { key , { type , value } } | invalid_claims ] }
221
+ end
222
+ end
223
+
224
+ defp cast_value ( value , :binary ) when is_binary ( value ) , do: { :ok , value }
225
+ defp cast_value ( value , :binary ) when is_integer ( value ) , do: { :ok , to_string ( value ) }
226
+
227
+ defp cast_value ( value , :integer ) when is_integer ( value ) , do: { :ok , value }
228
+
229
+ defp cast_value ( value , :integer ) when is_binary ( value ) do
230
+ case Integer . parse ( value ) do
231
+ { integer , "" } -> { :ok , integer }
232
+ _ -> :error
233
+ end
234
+ end
235
+
236
+ defp cast_value ( value , :boolean ) when is_boolean ( value ) , do: { :ok , value }
237
+
238
+ defp cast_value ( _value , _type ) , do: :error
239
+
146
240
@ doc """
147
241
Recursively prunes map for nil values.
148
242
"""
0 commit comments