001package ca.cdr.api.model.json;
002
003/*
004 * #%L
005 * Smile CDR - CDR
006 * %%
007 * Copyright (C) 2016 - 2026 Smile CDR, Inc.
008 * %%
009 * All rights reserved.
010 * #L%
011 */
012
013import ca.cdr.api.model.enm.PermissionEnum;
014import ca.cdr.api.model.enm.UserTwoFactorAuthEnum;
015import ca.cdr.api.model.json.jackson2deprecated.JsonDateDeserializerJacksonV2Deprecated;
016import ca.cdr.api.model.json.jackson2deprecated.JsonDateSerializerJacksonV2Deprecated;
017import ca.cdr.api.model.json.jackson2deprecated.JsonDateWithEmptyDeserializerJacksonV2Deprecated;
018import ca.uhn.fhir.context.ConfigurationException;
019import ca.uhn.fhir.model.api.IModelJson;
020import com.fasterxml.jackson.annotation.JsonProperty;
021import com.fasterxml.jackson.annotation.JsonPropertyOrder;
022import io.swagger.v3.oas.annotations.Operation;
023import io.swagger.v3.oas.annotations.Parameter;
024import io.swagger.v3.oas.annotations.media.Schema;
025import jakarta.annotation.Nonnull;
026import org.apache.commons.lang3.SerializationUtils;
027import org.apache.commons.lang3.Validate;
028import org.apache.commons.lang3.builder.ToStringBuilder;
029import org.apache.commons.lang3.builder.ToStringStyle;
030import org.hl7.fhir.dstu3.model.DateType;
031import org.springframework.security.core.userdetails.UserDetails;
032import tools.jackson.databind.annotation.JsonDeserialize;
033import tools.jackson.databind.annotation.JsonSerialize;
034
035import java.io.Serializable;
036import java.util.ArrayList;
037import java.util.Collection;
038import java.util.Date;
039import java.util.List;
040import java.util.Optional;
041import java.util.Set;
042import java.util.TreeSet;
043import java.util.stream.Collectors;
044
045import static org.apache.commons.lang3.StringUtils.defaultString;
046import static org.apache.commons.lang3.StringUtils.isNotBlank;
047
048/**
049 * Perhaps if anyone is wondering why we have such a vague fields such as associatedResources and/or defaultLaunchContexts, in our model in the first place.
050 * It's because the SMART spec has this concept of launch contexts, where the launch context is simply the patient/encounter/location that should
051 * be opened when the app starts.
052 * And there's nothing more implied there, just "this is what you should open", so the reason could be anything:
053 * - the user is that patient
054 * - the user has picked that patient from a list
055 * - the user is the parent of the patient
056 * - etc
057 */
058@Schema(name = "UserDetails", description = "A user definition")
059@JsonPropertyOrder({
060        "pid",
061        "nodeId",
062        "moduleId",
063        "username",
064        "familyName",
065        "givenName",
066        "notes",
067        "lastActive",
068        "lastConnected",
069        "credentialExpiry",
070        "accountLocked",
071        "systemUser",
072        "authorities",
073        "associatedResources",
074        "defaultLaunchContexts"
075})
076public class UserDetailsJson implements UserDetails, Cloneable, IHasAuthorities, ca.uhn.fhir.model.api.IModelJson {
077
078        private static final long serialVersionUID = 2L;
079
080        /*
081         * ******************************************************************
082         * Note: If you add properties, add them to the copy constructor too!
083         * ******************************************************************
084         */
085
086        @JsonProperty("accountDisabled")
087        @Parameter(description = "Is this account currently disabled?")
088        private Boolean myAccountDisabled;
089
090        @JsonProperty("notes")
091        @Parameter(description = "Any notes regarding this user")
092        private String myNotes;
093
094        @JsonProperty("email")
095        @Parameter(description = "The user email address")
096        private String myEmail;
097
098        @JsonProperty("accountExpiry")
099        @com.fasterxml.jackson.databind.annotation.JsonSerialize(using = JsonDateSerializerJacksonV2Deprecated.class)
100        @JsonSerialize(using = JsonDateSerializer.class)
101        @com.fasterxml.jackson.databind.annotation.JsonDeserialize(
102                        using = JsonDateWithEmptyDeserializerJacksonV2Deprecated.class)
103        @JsonDeserialize(using = JsonDateWithEmptyDeserializer.class)
104        @Parameter(description = "The expiry date for the user (if any) or null if the account should not expire")
105        private Date myAccountExpiry;
106
107        @JsonProperty("accountLocked")
108        @Parameter(description = "Is this account currently locked?")
109        private Boolean myAccountLocked;
110
111        @JsonProperty("failedLoginAttempts")
112        @Parameter(description = "Number of consecutive failed login attempts")
113        private int myFailedLoginAttempts;
114
115        @JsonProperty("authorities")
116        @Parameter(description = "Any authorities (permissions) granted to this user")
117        private List<GrantedAuthorityJson> myAuthorities;
118
119        @JsonProperty("associatedResources")
120        @Schema(
121                        description =
122                                        "A collection of \"associated resource\" IDs. Associated resources are FHIR resources with some connection to the given user, such as a Patient or Practitioner resource representing the actual user.",
123                        accessMode = Schema.AccessMode.READ_ONLY)
124        private List<AssociatedResourceJson> myAssociatedResources;
125
126        @JsonProperty("credentialExpiry")
127        @com.fasterxml.jackson.databind.annotation.JsonSerialize(using = JsonDateSerializerJacksonV2Deprecated.class)
128        @JsonSerialize(using = JsonDateSerializer.class)
129        @com.fasterxml.jackson.databind.annotation.JsonDeserialize(
130                        using = JsonDateWithEmptyDeserializerJacksonV2Deprecated.class)
131        @JsonDeserialize(using = JsonDateWithEmptyDeserializer.class)
132        @Parameter(description = "If set, provides the date that the user credentials will expire")
133        private Date myCredentialExpiry;
134
135        @JsonProperty("familyName")
136        @Parameter(description = "The user's family (last) name")
137        private String myFamilyName;
138
139        @JsonProperty("givenName")
140        @Parameter(description = "The user's given (first) name")
141        private String myGivenName;
142
143        @JsonProperty(value = "lastActive")
144        @com.fasterxml.jackson.databind.annotation.JsonSerialize(using = JsonDateSerializerJacksonV2Deprecated.class)
145        @JsonSerialize(using = JsonDateSerializer.class)
146        @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = JsonDateDeserializerJacksonV2Deprecated.class)
147        @JsonDeserialize(using = JsonDateDeserializer.class)
148        @Schema(
149                        accessMode = Schema.AccessMode.READ_ONLY,
150                        description =
151                                        "The date at which the user account was last used. Note that this property is read-only, and is only updated once per day, so it is accurate only to the date.")
152        private Date myLastActive;
153
154        @JsonProperty(value = "lastConnected")
155        @com.fasterxml.jackson.databind.annotation.JsonSerialize(using = JsonDateSerializerJacksonV2Deprecated.class)
156        @JsonSerialize(using = JsonDateSerializer.class)
157        @com.fasterxml.jackson.databind.annotation.JsonDeserialize(using = JsonDateDeserializerJacksonV2Deprecated.class)
158        @JsonDeserialize(using = JsonDateDeserializer.class)
159        @Schema(
160                        accessMode = Schema.AccessMode.READ_ONLY,
161                        description =
162                                        "The date at which the user last logged in. This property is read-only and is accurate to the minute.")
163        private Date myLastConnected;
164
165        @Schema(
166                        accessMode = Schema.AccessMode.READ_ONLY,
167                        description =
168                                        "The module ID associated with this user account. This is the module ID associated with the Inbound Security module that is responsible for authenticating this user.")
169        @JsonProperty("moduleId")
170        private String myModuleId;
171
172        @Schema(
173                        accessMode = Schema.AccessMode.READ_ONLY,
174                        description =
175                                        "The node ID associated with this user. This is the master node ID associated with the Inbound Security module that is responsible for authenticating this user.")
176        @JsonProperty("nodeId")
177        private String myNodeId;
178
179        @JsonProperty("password")
180        @Schema(
181                        description =
182                                        "The user password (note that this property will not be populated when sessions are made available to user code)",
183                        readOnly = true)
184        private String myPassword;
185
186        @JsonProperty("pid")
187        @Schema(accessMode = Schema.AccessMode.READ_ONLY, description = "The PID (internal ID) for this user")
188        private Long myPid;
189
190        @JsonProperty("username")
191        @Schema(accessMode = Schema.AccessMode.READ_ONLY, description = "The username for this user")
192        private String myUsername;
193
194        @JsonProperty("usernameNamespace")
195        @Schema(accessMode = Schema.AccessMode.READ_ONLY, description = "The username namespace associated with this user")
196        private String myUsernameNamespace;
197
198        @Schema(
199                        accessMode = Schema.AccessMode.READ_ONLY,
200                        description =
201                                        "If this is set, the user cannot be renamed or deleted (this property may only be set by the system)")
202        @JsonProperty("systemUser")
203        private boolean mySystemUser;
204
205        @Schema(
206                        accessMode = Schema.AccessMode.READ_ONLY,
207                        description =
208                                        "If this value is set, the user is backed by an external user directory (this property may only be set by the system)")
209        @JsonProperty("external")
210        private boolean myExternal;
211
212        @JsonProperty("defaultLaunchContexts")
213        @Schema(
214                        accessMode = Schema.AccessMode.READ_ONLY,
215                        description = "The SMART launch contexts associated with this account")
216        private List<LaunchContextJson> myDefaultLaunchContexts;
217
218        @JsonProperty("serviceAccount")
219        private Boolean myServiceAccount;
220
221        @JsonProperty("twoFactorAuthStatus")
222        private UserTwoFactorAuthEnum myTwoFactorAuthStatus;
223
224        /**
225         * Constructor
226         */
227        public UserDetailsJson() {
228                super();
229        }
230
231        /**
232         * Constructor
233         */
234        public UserDetailsJson(UserDetailsJson theCopyObject) {
235                this();
236
237                setPid(theCopyObject.getPid());
238
239                setUsername(theCopyObject.getUsername());
240                setUsernameNamespace(theCopyObject.getUsernameNamespace());
241                setPassword(theCopyObject.getPassword());
242
243                setNodeId(theCopyObject.getNodeId());
244                setModuleId(theCopyObject.getModuleId());
245
246                setFamilyName(theCopyObject.getFamilyName());
247                setGivenName(theCopyObject.getGivenName());
248                setEmail(theCopyObject.getEmail());
249
250                setNotes(theCopyObject.getNotes());
251                setFailedLoginAttempts(theCopyObject.getFailedLoginAttempts());
252                setAccountExpiry(theCopyObject.getAccountExpiry());
253                setAccountDisabled(theCopyObject.isAccountDisabled());
254                setAccountLocked(theCopyObject.isAccountLocked());
255                setServiceAccount(theCopyObject.isServiceAccount());
256                setSystemUser(theCopyObject.isSystemUser());
257                setCredentialExpiry(theCopyObject.getCredentialExpiry());
258                setLastActive(theCopyObject.getLastActive());
259                setLastConnected(theCopyObject.getLastConnected());
260
261                setAuthorities(theCopyObject.getAuthorities());
262                setExternal(theCopyObject.isExternal());
263
264                setTwoFactorAuthStatus(theCopyObject.getTwoFactorAuthStatus());
265
266                getDefaultLaunchContexts().addAll(theCopyObject.getDefaultLaunchContexts());
267                getAssociatedResources().addAll(theCopyObject.getAssociatedResources());
268        }
269
270        /*
271         * ******************************************************************
272         * Note: If you add properties, add them to the copy constructor too!
273         * ******************************************************************
274         */
275
276        public UserTwoFactorAuthEnum getTwoFactorAuthStatus() {
277                return myTwoFactorAuthStatus;
278        }
279
280        public UserDetailsJson setTwoFactorAuthStatus(UserTwoFactorAuthEnum theTwoFactorAuthStatus) {
281                myTwoFactorAuthStatus = theTwoFactorAuthStatus;
282                return this;
283        }
284
285        public void addAssociatedResource(String theType, String theResourceId) {
286                Validate.notBlank(theType, "The type must not be null");
287                AssociatedResourceTypeEnum type;
288                try {
289                        type = AssociatedResourceTypeEnum.valueOf(theType);
290                } catch (Exception e) {
291                        throw new ConfigurationException("Invalid type: " + theType);
292                }
293                addAssociatedResource(type, theResourceId);
294        }
295
296        public void addAssociatedResource(AssociatedResourceTypeEnum theType, String theResourceId) {
297                Validate.notNull(theType, "The type must not be null");
298                Validate.notBlank(theResourceId, "The resource ID must not be null");
299
300                getAssociatedResources()
301                                .add(new AssociatedResourceJson()
302                                                .setType(AssociatedResourceTypeEnum.SELF)
303                                                .setResourceId(theResourceId));
304        }
305
306        public void addAuthorities(GrantedAuthorityJson... theAuthority) {
307                for (GrantedAuthorityJson next : theAuthority) {
308                        addAuthority(next);
309                }
310        }
311
312        public UserDetailsJson addAuthority(PermissionEnum thePermission) {
313                getAuthorities().add(new GrantedAuthorityJson(thePermission));
314                return this;
315        }
316
317        public void addAuthority(GrantedAuthorityJson theAuthority) {
318                Validate.notNull(theAuthority, "theAuthority must not be null");
319                getAuthorities().add(theAuthority);
320        }
321
322        public LaunchContextJson addDefaultLaunchContext() {
323                LaunchContextJson ctx = new LaunchContextJson();
324                getDefaultLaunchContexts().add(ctx);
325                return ctx;
326        }
327
328        @SuppressWarnings("MethodDoesntCallSuperMethod")
329        @Override
330        public UserDetailsJson clone() {
331                return SerializationUtils.clone(this);
332        }
333
334        public Date getAccountExpiry() {
335                return myAccountExpiry;
336        }
337
338        public void setAccountExpiry(Date theAccountExpiry) {
339                myAccountExpiry = theAccountExpiry;
340        }
341
342        public boolean hasAccountExpiryDate() {
343                return myAccountExpiry != null;
344        }
345
346        public String getAccountExpiryDateAsIsoString() {
347                if (myAccountExpiry == null) {
348                        return "";
349                }
350                return new DateType(myAccountExpiry).getValueAsString();
351        }
352
353        public List<AssociatedResourceJson> getAssociatedResources() {
354                if (myAssociatedResources == null) {
355                        myAssociatedResources = new ArrayList<>();
356                }
357                return myAssociatedResources;
358        }
359
360        public boolean hasAuthorities() {
361                return myAuthorities != null;
362        }
363
364        @Override
365        @Nonnull
366        public List<GrantedAuthorityJson> getAuthorities() {
367                if (myAuthorities == null) {
368                        myAuthorities = new ArrayList<>();
369                }
370                return myAuthorities;
371        }
372
373        public UserDetailsJson setAuthorities(Collection<GrantedAuthorityJson> theAuthorities) {
374                myAuthorities = new ArrayList<>(theAuthorities);
375                return this;
376        }
377
378        public boolean hasCredentialExpiryDate() {
379                return myCredentialExpiry != null;
380        }
381
382        public Date getCredentialExpiry() {
383                return myCredentialExpiry;
384        }
385
386        public void setCredentialExpiry(Date theCredentialExpiry) {
387                myCredentialExpiry = theCredentialExpiry;
388        }
389
390        public String getCredentialExpiryDateAsIsoString() {
391                if (myCredentialExpiry == null) {
392                        return "";
393                }
394                return new DateType(myCredentialExpiry).getValueAsString();
395        }
396
397        public boolean hasDefaultLaunchContexts() {
398                return myDefaultLaunchContexts != null;
399        }
400
401        public List<LaunchContextJson> getDefaultLaunchContexts() {
402                if (myDefaultLaunchContexts == null) {
403                        myDefaultLaunchContexts = new ArrayList<>();
404                }
405                return myDefaultLaunchContexts;
406        }
407
408        public Optional<String> getDefaultLaunchContextResourceIdForContextType(@Nonnull String theContextType) {
409                for (LaunchContextJson contextParam : getDefaultLaunchContexts()) {
410                        if (theContextType.equalsIgnoreCase(contextParam.getContextType())) {
411                                return Optional.of(contextParam.getResourceId());
412                        }
413                }
414
415                return Optional.empty();
416        }
417
418        @SuppressWarnings("unused")
419        public String getDefaultLaunchContextsCommaSeparated(String theType) {
420                StringBuilder b = new StringBuilder();
421                getDefaultLaunchContexts().stream()
422                                .filter(t -> t.getContextType().equals(theType))
423                                .filter(t -> isNotBlank(t.getResourceId()))
424                                .forEach(t -> {
425                                        if (!b.isEmpty()) {
426                                                b.append(", ");
427                                        }
428                                        b.append(t.getResourceId());
429                                });
430                return b.toString();
431        }
432
433        public boolean hasEmail() {
434                return myEmail != null;
435        }
436
437        public String getEmail() {
438                return myEmail;
439        }
440
441        public UserDetailsJson setEmail(String theEmail) {
442                myEmail = theEmail;
443                return this;
444        }
445
446        public boolean hasFamilyName() {
447                return myFamilyName != null;
448        }
449
450        public String getFamilyName() {
451                return myFamilyName;
452        }
453
454        public UserDetailsJson setFamilyName(String theFamilyName) {
455                myFamilyName = theFamilyName;
456                return this;
457        }
458
459        public String getFullName() {
460                return (defaultString(myGivenName) + ' ' + defaultString(myFamilyName)).trim();
461        }
462
463        public boolean hasGivenName() {
464                return myGivenName != null;
465        }
466
467        public String getGivenName() {
468                return myGivenName;
469        }
470
471        public UserDetailsJson setGivenName(String theGivenName) {
472                myGivenName = theGivenName;
473                return this;
474        }
475
476        public Date getLastActive() {
477                return myLastActive;
478        }
479
480        public void setLastActive(Date theLastActive) {
481                myLastActive = theLastActive;
482        }
483
484        public Date getLastConnected() {
485                return myLastConnected;
486        }
487
488        public void setLastConnected(Date theLastConnected) {
489                myLastConnected = theLastConnected;
490        }
491
492        public String getModuleId() {
493                return myModuleId;
494        }
495
496        public UserDetailsJson setModuleId(String theModuleId) {
497                myModuleId = theModuleId;
498                return this;
499        }
500
501        public String getNodeId() {
502                return myNodeId;
503        }
504
505        public UserDetailsJson setNodeId(String theNodeId) {
506                myNodeId = theNodeId;
507                return this;
508        }
509
510        public boolean hasNotes() {
511                return myNotes != null;
512        }
513
514        public String getNotes() {
515                return myNotes;
516        }
517
518        public void setNotes(String theNotes) {
519                myNotes = theNotes;
520        }
521
522        @Override
523        public String getPassword() {
524                return myPassword;
525        }
526
527        public UserDetailsJson setPassword(String thePassword) {
528                myPassword = thePassword;
529                return this;
530        }
531
532        public boolean hasPassword() {
533                return myPassword != null;
534        }
535
536        public Long getPid() {
537                return myPid;
538        }
539
540        public UserDetailsJson setPid(Long thePid) {
541                myPid = thePid;
542                return this;
543        }
544
545        public Set<String> getAuthorityNames() {
546                return getAuthorities().stream()
547                                .map(t -> t.getPermission().name())
548                                .collect(Collectors.toCollection(TreeSet::new));
549        }
550
551        public Boolean getServiceUser() {
552                if (myServiceAccount == null) {
553                        return false;
554                }
555                return myServiceAccount;
556        }
557
558        public boolean hasUsername() {
559                return myUsername != null;
560        }
561
562        @Override
563        public String getUsername() {
564                return myUsername;
565        }
566
567        public UserDetailsJson setUsername(String theUsername) {
568                myUsername = theUsername;
569                return this;
570        }
571
572        @Override
573        public List<GrantedAuthorityJson> getPermissions() {
574                return getAuthorities();
575        }
576
577        @Operation(summary = "hasAuthority", description = "Does the user have the given permission?")
578        public boolean hasAuthority(
579                        @Parameter(name = "thePermission", description = "The name of the permission, e.g. 'ROLE_FHIR_CLIENT'")
580                                        String thePermission) {
581                for (GrantedAuthorityJson next : getAuthorities()) {
582                        if (next.getPermission().name().equals(thePermission)) {
583                                return true;
584                        }
585                }
586                return false;
587        }
588
589        public boolean hasAccountDisabled() {
590                return myAccountDisabled != null;
591        }
592
593        public boolean isAccountDisabled() {
594                if (myAccountDisabled == null) {
595                        return false;
596                }
597                return myAccountDisabled;
598        }
599
600        public void setAccountDisabled(boolean theAccountDisabled) {
601                myAccountDisabled = theAccountDisabled;
602        }
603
604        public boolean hasAccountLocked() {
605                return myAccountLocked != null;
606        }
607
608        public boolean isAccountLocked() {
609                if (myAccountLocked == null) {
610                        return false;
611                }
612                return myAccountLocked;
613        }
614
615        public void lockAccount() {
616                setAccountLocked(true);
617        }
618
619        public void setAccountLocked(boolean theAccountLocked) {
620                myAccountLocked = theAccountLocked;
621        }
622
623        public void setFailedLoginAttempts(int theFailedLoginAttempts) {
624                myFailedLoginAttempts = theFailedLoginAttempts;
625        }
626
627        public int getFailedLoginAttempts() {
628                return myFailedLoginAttempts;
629        }
630
631        public int incrementFailedLoginAttempts() {
632                setFailedLoginAttempts(getFailedLoginAttempts() + 1);
633                return getFailedLoginAttempts();
634        }
635
636        public void resetFailedLoginAttempts() {
637                setFailedLoginAttempts(0);
638        }
639
640        @Override
641        public boolean isAccountNonExpired() {
642                return myAccountExpiry == null || myAccountExpiry.getTime() > System.currentTimeMillis();
643        }
644
645        @Override
646        public boolean isAccountNonLocked() {
647                return !isAccountLocked();
648        }
649
650        @Override
651        public boolean isCredentialsNonExpired() {
652                return myCredentialExpiry == null || myCredentialExpiry.getTime() > System.currentTimeMillis();
653        }
654
655        @Override
656        public boolean isEnabled() {
657                return !isAccountDisabled();
658        }
659
660        public boolean isExternal() {
661                return myExternal;
662        }
663
664        public void setExternal(boolean theExternal) {
665                myExternal = theExternal;
666        }
667
668        public boolean hasServiceAccount() {
669                return myServiceAccount != null;
670        }
671
672        public boolean isServiceAccount() {
673                if (myServiceAccount == null) {
674                        return false;
675                }
676                return myServiceAccount;
677        }
678
679        public void setServiceAccount(boolean theServiceAccount) {
680                myServiceAccount = theServiceAccount;
681        }
682
683        public boolean isSystemUser() {
684                return mySystemUser;
685        }
686
687        public UserDetailsJson setSystemUser(boolean theSystemUser) {
688                mySystemUser = theSystemUser;
689                return this;
690        }
691
692        @Override
693        public String toString() {
694                ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.NO_CLASS_NAME_STYLE);
695                b.append("pid", myPid);
696                b.append("username", myUsername);
697                b.append("familyName", myFamilyName);
698                b.append("givenName", myGivenName);
699                b.append("authorities", myAuthorities);
700                b.append("lastActive", myLastActive);
701                b.append("lastConnected", myLastConnected);
702                b.append("failedLoginAttempts", myFailedLoginAttempts);
703                if (myAccountDisabled != null && myAccountDisabled) {
704                        b.append("accountDisabled", myAccountDisabled);
705                }
706                if (myAccountExpiry != null) {
707                        b.append("accountExpiryDate", myAccountExpiry);
708                }
709                if (myAccountLocked != null && myAccountLocked) {
710                        b.append("accountLocked", myAccountLocked);
711                }
712                if (myCredentialExpiry != null) {
713                        b.append("credentialExpiryDate", myCredentialExpiry);
714                }
715                if (mySystemUser) {
716                        b.append("systemUser", mySystemUser);
717                }
718                if (myExternal) {
719                        b.append("external", myExternal);
720                }
721                b.append("nodeId", myNodeId);
722                b.append("moduleId", myModuleId);
723                return b.toString();
724        }
725
726        public String getUsernameNamespace() {
727                return myUsernameNamespace;
728        }
729
730        public void setUsernameNamespace(String theUsernameNamespace) {
731                myUsernameNamespace = theUsernameNamespace;
732        }
733
734        public boolean hasUsernameNamespace() {
735                return myUsernameNamespace != null;
736        }
737
738        @Operation(
739                        summary = "getOrCreateDefaultLaunchContext",
740                        description = "Returns the first default launch context for the given type, creating one if none exists")
741        public LaunchContextJson getOrCreateDefaultLaunchContext(
742                        @Parameter(name = "theContextType", description = "The context type, e.g. \"patient\" or \"practitioner\"")
743                                        String theContextType) {
744                for (LaunchContextJson next : getDefaultLaunchContexts()) {
745                        if (next.getContextType().equals(theContextType)) {
746                                return next;
747                        }
748                }
749
750                LaunchContextJson retVal = new LaunchContextJson().setContextType(theContextType);
751                getDefaultLaunchContexts().add(retVal);
752                return retVal;
753        }
754
755        @Operation(
756                        summary = "getOrCreateDefaultLaunchContext",
757                        description = "Returns the first default launch context for the given type, creating one if none exists")
758        public LaunchContextJson getOrCreateDefaultLaunchContext(
759                        @Parameter(name = "theContextType", description = "The context type, e.g. \"patient\" or \"practitioner\"")
760                                        String theContextType,
761                        @Parameter(name = "theIndex", description = "The index, starting at 0") int theIndex) {
762
763                int foundIndex = 0;
764                for (LaunchContextJson next : getDefaultLaunchContexts()) {
765                        if (next.getContextType().equals(theContextType)) {
766                                if (theIndex == foundIndex) {
767                                        return next;
768                                } else {
769                                        foundIndex++;
770                                }
771                        }
772                }
773
774                LaunchContextJson retVal = new LaunchContextJson().setContextType(theContextType);
775                getDefaultLaunchContexts().add(retVal);
776                return retVal;
777        }
778
779        /**
780         * What type of relationship does the user have to the
781         * given resource
782         */
783        public enum AssociatedResourceTypeEnum {
784
785                /**
786                 * This associated resource is the user themself
787                 */
788                SELF
789        }
790
791        /**
792         * This structure represents a link between a user in the auth database
793         * and a resource in the FHIR database. This can be used, for example,
794         * to specify that a particular user is a specific Patient in the
795         * CDR. That linkage can then be applied in order to make
796         * security/permission decisions.
797         */
798        @Schema(
799                        name = "AssociatedResource",
800                        description = "This structure represents a link between a user in the auth database "
801                                        + "and a resource in the FHIR database. This can be used, for example, "
802                                        + "to specify that a particular user is a specific Patient in the "
803                                        + "CDR. That linkage can then be applied in order to make "
804                                        + "security/permission decisions.")
805        public static class AssociatedResourceJson implements Serializable, IModelJson {
806
807                @JsonProperty("type")
808                @Schema(description = "The relationship between the user and the resource")
809                private AssociatedResourceTypeEnum myType;
810
811                @JsonProperty("resourceId")
812                @Schema(description = "The resource ID itself, e.g. 'Patient/123'", example = "Patient/123")
813                private String myResourceId;
814
815                public String getResourceId() {
816                        return myResourceId;
817                }
818
819                public AssociatedResourceJson setResourceId(String theResourceId) {
820                        myResourceId = theResourceId;
821                        return this;
822                }
823
824                public AssociatedResourceTypeEnum getType() {
825                        return myType;
826                }
827
828                public AssociatedResourceJson setType(String theType) {
829                        myType = AssociatedResourceTypeEnum.valueOf(theType);
830                        return this;
831                }
832
833                public AssociatedResourceJson setType(AssociatedResourceTypeEnum theType) {
834                        myType = theType;
835                        return this;
836                }
837        }
838
839        public static PermissionEnum toPermissionEnum(String thePermission) {
840                try {
841                        return PermissionEnum.valueOf(thePermission);
842                } catch (Exception e) {
843                        throw new ConfigurationException("Invalid permission name: " + thePermission);
844                }
845        }
846}