@@ -110,6 +110,20 @@ impl ConfigurationSection for UpstreamOAuth2Config {
110110 }
111111 }
112112 }
113+
114+ if !provider. claims_imports . localpart . on_conflict . is_default ( )
115+ && !matches ! (
116+ provider. claims_imports. localpart. action,
117+ ImportAction :: Force | ImportAction :: Require
118+ )
119+ {
120+ return annotate ( figment:: Error :: custom (
121+ "When `on_conflict` is used to resolved conflicts, `localpart` claim import must be either `force` or `require`" ,
122+ ) ) ;
123+ }
124+
125+ //TODO : check that claims imports use on_conflict where it is not
126+ // supported?
113127 }
114128
115129 Ok ( ( ) )
@@ -183,6 +197,26 @@ impl ImportAction {
183197 }
184198}
185199
200+ /// How to handle an existing localpart claim
201+ #[ derive( Debug , Clone , Copy , PartialEq , Eq , Serialize , Deserialize , Default , JsonSchema ) ]
202+ #[ serde( rename_all = "lowercase" ) ]
203+ pub enum OnConflict {
204+ /// Fails the sso login on conflict
205+ #[ default]
206+ Fail ,
207+
208+ /// Adds the oauth identity link, regardless of whether there is an existing
209+ /// link or not
210+ Add ,
211+ }
212+
213+ impl OnConflict {
214+ #[ allow( clippy:: trivially_copy_pass_by_ref) ]
215+ const fn is_default ( & self ) -> bool {
216+ matches ! ( self , OnConflict :: Fail )
217+ }
218+ }
219+
186220/// What should be done for the subject attribute
187221#[ derive( Debug , Clone , PartialEq , Eq , Serialize , Deserialize , Default , JsonSchema ) ]
188222pub struct SubjectImportPreference {
@@ -211,6 +245,10 @@ pub struct LocalpartImportPreference {
211245 /// If not provided, the default template is `{{ user.preferred_username }}`
212246 #[ serde( default , skip_serializing_if = "Option::is_none" ) ]
213247 pub template : Option < String > ,
248+
249+ /// How to handle conflicts on the claim, default value is `Fail`
250+ #[ serde( default , skip_serializing_if = "OnConflict::is_default" ) ]
251+ pub on_conflict : OnConflict ,
214252}
215253
216254impl LocalpartImportPreference {
0 commit comments